아래와 같은 관계를 가지는 두 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);
}
}
}
연관관계 편의 메소드
를 작성할 때 주의사항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
와 setGroup
method에서 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);
if(this.group != null)
은 빠져나간다.this.group = group
으로 group 객체의 주소값을 참조한다.addUser(user)
를 호출한다.List<User> users
에 user를 add한다.setUser(group)
을 호출한다.if(this.group != null)
은 group을 참조하므로 group에서 user를 제거한다.this.group = group
으로 group 객체의 주소값을 참조한다.addUser(user)
를 호출한다.따라서 setGroup의 if(!group.getUsers().contains(this))
,
addUser의 if (user.getGroup != this)
로직과 같이
무한루프에 빠지지않게 체크해주는 로직을 반드시 추가해야 한다.
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 쑤ㅠㅃ!ㅏ