양방향 매핑
아래와 같은 관계를 가지는 두 TABLE이 있다고 하자.
USER와 GROUP은 다대일 관계이고,
GROUP에서 USER는 일대다 관계이다.
위 두 TABLE을 JPA에서 Entity로 Mapping할때
일대다 관계의 경우 여러 건과 연관관계를 맺을 수 있으므로, Collection을 사용해야한다.
JPA는 List 포함 Collection, Set, Map 등을 지원
위 관계에서 USER, GROUP Entity 예시는 아래와 같다.
@Entity
public class User {
@Id
@Column(name = "U_ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@ManyToOne
@JoinColumn(name = "G_ID")
private Group group;
public void setGroup(Group group) {
if(this.group != null) {
this.group.getUser().remove(this)
}
this.group = group;
group.getUsers().add(this);
}
}
@Entity
public class Group {
@Id
@Column(name = "G_ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@OneToMany(mappedBy = "group")
private List<User> users = new ArrayList<User>();
public void addUser(User user) {
this.users.add(user);
if (user.getGroup != this) {
user.setGroup(this);
}
}
}
다대일 관계인 USER 측에는 @ManyToOne Annotation을,
일대다 관계인 GROUP측에는 @OneToMany Annotation을 사용해주면 되는데,
이렇게 양방향 관계를 맺게되면 연관관계의 주인을 지정해주어야 한다.
일반적으로 외래키가 저장되는 다 측을(USER) 연관관계의 주인으로 지정하는데,
@JoinColumn Annotation으로 해당 Table에서 외래키인 G_ID를 name 속성으로 지정해주면 된다.
@ManyToOne
@JoinColumn(name = "G_ID")
private Group group;
한편 연관관계 주인의 반대 Entity(GROUP) 측에는 mappedBy를 지정해주어야 한다.
연관관계 주인에서 설정한 Field 명인 group을 mappedBy로 설정한다.
@OneToMany(mappedBy = "group")
private List<User> users = new ArrayList<User>();
@ManyToOne은 mappedBy 속성을 설정할 수도 없음, 항상 외래키가 저장되어 연관관계 주인이 되기 때문
양방향 매핑 정리
단순하게 정리하면 양방향 매핑에서 아래를 생각하면서 매핑하면된다.
- 두 객체 연관관계 중 테이블 외래키를 관리하는 쪽을 연관관계의 주인이라 함.
보통 외래키가 저장되는 테이블을 연관관계 주인으로 설정 - 연관관계 주인만이 DB 연관관계와 매핑되고, 외래키를 관리(등록, 수정, 삭제)할 수 있음.
- 연관관계 주인은 mappedBy 속성을 사용하지 않음.
- 연관관계 주인이 아니면 mappedBy 속성으로 연관관계의 주인을 지정해주어야 함.
- @ManyToOne은 항상 연관관계의 주인이되고, mappedBy 속성을 설정 조차 할 수 없음.
DB 다대일 관계에서 다측에 외래키가 저장되기 때문
연관관계 편의 메소드
양방향 연관관계는 양 쪽 객체를 모두 신경써야 하는데,
하나의 메소드에서 양측에 관계를 설정하게 해주는 것이 안전하다.
이렇게 한번에 양방향 관계를 설정하는 메소드를 연관관계 편의 메소드 라 부른다.
아까전 User와 Group Entity에서 아래 메소드가 연관관계 편의 메소드이다.
public class User {
...
public void setGroup(Group group) {
if(this.group != null) {
this.group.getUser().remove(this)
}
this.group = group;
group.getUsers().add(this);
}
}
@Entity
public class Group {
...
public void addUser(User user) {
this.users.add(user);
if (user.getGroup != this) {
user.setGroup(this);
}
}
}
연관관계 편의 메소드를 작성할 때 주의사항
- 다대일측(User)에서 연관관계를 지정할 때 기존 연관관계는 끊어주어야 한다.
public class User {
...
public void setGroup(Group group) {
// 이 부분에서 기존에 Group과 연관관계가 있다면
// Group에서 해당 User를 먼저 제거한다.
if(this.group != null) {
this.group.getUser().remove(this)
}
this.group = group;
group.getUsers().add(this);
}
}
- 연관관계를 저장할 때 무한 루프를 주의해야 한다.
public class User {
...
public void setGroup(Group group) {
if(this.group != null) {
this.group.getUser().remove(this)
}
this.group = group;
// 이부분이 없으면 무한 루프에 걸린다.
if(!group.getUsers().contains(this)) {
group.addUser(this);
}
}
}
@Entity
public class Group {
...
public void addUser(User user) {
this.users.add(user);
// 이부분이 없으면 무한 루프에 걸린다.
if (user.getGroup != this) {
user.setGroup(this);
}
}
}
만약 addUser와 setGroupmethod에서 if 부분 로직을 제거 하면 아래와 같다.
public class User {
...
public void setGroup(Group group) {
if(this.group != null) {
this.group.getUser().remove(this)
}
this.group = group;
group.addUser(this);
}
}
@Entity
public class Group {
...
public void addUser(User user) {
this.users.add(user);
user.setGroup(this);
}
}
만약 아래를 호출해보면 아래 과정으로 무한루프에 빠진다.
User user = new User();
Group group = new Group();
user.setGroup(group);
- setGroup의 if(this.group != null)은 빠져나간다.
- user의 group은 this.group = group으로 group 객체의 주소값을 참조한다.
- group의 addUser(user)를 호출한다.
- group의 List<User> users에 user를 add한다.
- user의 setUser(group)을 호출한다.
- setGroup의 if(this.group != null)은 group을 참조하므로 group에서 user를 제거한다.
- user의 group은 this.group = group으로 group 객체의 주소값을 참조한다.
- group의 addUser(user)를 호출한다.
....
따라서 setGroup의 if(!group.getUsers().contains(this)),
addUser의 if (user.getGroup != this) 로직과 같이
무한루프에 빠지지않게 체크해주는 로직을 반드시 추가해야 한다.
- toString()에서도 무한 루프에 걸릴 수 있다.
lombok을 사용하다 가볍게 @ToString을 적었다가 낭패를 볼 수 있다.
만약 User에도 toString이 있고, Group에도 toString이 있다면 마찬가지로 무한 루프에 걸릴 수 있으므로 주의해야한다.
User user = new User();
Group group = new Group();
user.setGroup(group);
user.toString();
/////// toString 예시
#####user#####
id=1
userId=gillog
group=
#####group####
id=1
name=java
users={####user####
....
'JPA' 카테고리의 다른 글
Entity 올바른 수정은 무엇인가 ? (1) | 2023.01.12 |
---|---|
JPA 올바른 Entity 생성 (0) | 2023.01.09 |
JPA 영속성 컨텍스트란? (0) | 2023.01.08 |
JPA 영속성관리 , N+1 문제 발생 원인 및 해결방법 (0) | 2023.01.07 |
JAVA Stream을 이용해서 모든회원정보 추출 (0) | 2023.01.07 |