public void testDomain() {
Team team1 = new Team("team1", "팀1");
Member member1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");
member1.setTeam(team1); // member1 -> team1
team1.getMembers().add(member1); // team1 -> member1
member2.setTeam(team2); // member2 -> team1
team1.getMembers().add(member2); // team1 -> member2
}
양방향이라면 양쪽 다 관계를 설정해주어야 한다.
외래키 주인만 매핑해줘도 데이터베이스 상에서는 문제 없지만, 순수한 객체 상태에서 문제가 발생할 수 있다.
Member.team : 연관관계의 주인, 해당 값으로 외래 키 관리
Team.members : 연관관계의 주인이 아니므로 저장 시에 사용되지는 않는다.
📌 연관관계 편의 메서드
public class Member {
private Team team;
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(member);
}
}
양방향 매핑으로 인한 중복 코드로 인해 코드가 지저분해지고, 오류가 발생할 확률이 높아진다.
연관관계의 주인인 Member 클래스에서 도우미 메서드를 호출하는 쪽이 좋다.
public void testDomain() {
Team team1 = new Team("team1", "팀1");
Member member1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");
member1.setTeam(team1);
member2.setTeam(team2);
}
📌 연관관계 편의 메서드 주의사항
위 방식을 그대로 사용하면 버그가 생긴다.
member1.setTeam(teamA);
member1.setTeam(teamB);
Member findMember = teamA.getMember();
기존에 매핑되어 있던 teamA와 매핑을 끊어주지 않았다.
따라서, getMember()를 호출하면 teamA가 같이 조회되는 문제가 발생한다.
public class Member {
private Team team;
public void setTeam(Team team) {
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(member);
}
}
기존에 매핑된 팀이 있으면 연관관계를 삭제하는 코드를 추가해주어야 한다.
✒️ 양방향 연관관계 삭제 시
이 문제는 제법 오랫동안 날 괴롭혀 왔던 이슈였다. 뭐가 더 나은 해결책인지 모르겠어서.. 기본적으로 단방향 관계에서 연관관계를 삭제하기 위해서는 외래키 주인이 setFK(null)을 해주면 그만이었다. 그런데 양방향 매핑에서 도우미 메서드를 활용하다보니 null을 입력하면 문제가 발생한다.
public void setTeam(Team team) {
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(member);
}
member.setTeam(null); // (null).getMembers -> 에러
이렇게 되면 setTeam에서 team이 null인지를 체크해주는 로직도 처리해주어야 하는데, '이게 과연 맞을까?' 라는 의문이 머리에서 떠나질 않았다. 대체 왜 책에는 delete에 대한 내용이 정의되지 않은 걸까를 고민한 끝에 아래 댓글이 의문점을 해소해주었다.
https://www.inflearn.com/questions/16819/%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%ED%8E%B8%EC%9D%98%EB%A9%94%EC%86%8C%EB%93%9C-%EA%B4%80%EB%A0%A8-%EC%A7%88%EB%AC%B8%EC%9D%B4-%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4
기업 입장에서 사용자가 남긴 로그는 하나하나가 데이터고 자산이다. 그러다보니 delete라는 행위가 거의 없을 것이라는 생각을 하질 못했던 것 같다. 교재에서도 관련한 로직을 굳이 고려하지 않은 이유는 애초에 이런 비지니스 로직을 처리할 일이 거의 없기 때문일 것이라는 생각이 든다.
✒️ 양방향 매핑 시 무한루프를 조심하라. (@ToString)
lombok의 @ToString을 남용하다가 낭패를 본 적이 있었다. User와 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####
....
2. 다대다 관계 분리
일반적으로 다대다 관계는 미완성 관계로 취급한다.
따라서 이를 분리해줄 필요가 있는데, 두 가지 방법이 있다.
📌 복합 키 사용
@Entity
public class Member {
@Id @Column(name = "MEMBER_ID")
private String id;
@OneToMany(mappedBy = "member")
private List<MebmerProduct> memberProducts = new ArrayList<>();
...
}
@Entity
public class Product {
@Id @Column(name = "PRODUCT_ID")
private String id;
private String name;
}
@Endity
@IdClass(MemberProductId.class)
public class MemberProduct {
@Id @ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@Id @ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
private int orderAmount;
...
}
public class MemberProductId implements Serializalbe {
private String member;
private String product;
@Override public boolean equals(Object obj) {...}
@Override public int hashCode() { ... }
}
복합 기본키
JPA에서 복합키를 사용하려면 별도의 식별자 클래스(@IdClass)를 만들어야 한다.
식별 관계(Identifying Relationship) : 부모 테이블의 기본키를 받아서 자신의 기본키 + 외래키로 사용하는 것
식별자 클래스
복합 키는 별도의 식별자 클래스로 만들어야 한다.
Serializable을 구현해야 한다.
equals와 hashCode 메서드를 구현해야 한다. (기본 설계 원칙)
기본 생성자가 있어야 한다.
식별자 클래스는 public이어야 한다.
@EmbeddedId를 사용하는 방법도 있다.
📌 새로운 기본 키 사용
@Endity
public class MemberProduct {
@Id @GeneratedValue
@Column(name = "ORDER_ID")
private Long id;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
private int orderAmount;
...
}
식별자 클래스를 사용하지 않고, Order 클래스 별도의 pk 필드를 지정해주는 것도 좋은 방법이다.