[JPA] 다양한 연관관계 매핑

Hocaron·2022년 3월 8일
0

Spring

목록 보기
17/44

연관관계 매핑시 고려해야할 사항

다중성

  • 다대일(@ManyToOne)
  • 일대다(@OneToMay)
  • 일대일(@OneToOne)
  • 다대다(@ManyToMany)

다대다 관계는 실무에서 거의 사용하지 않는데, 그 이유는?
@ManyToMany를 사용하면 연결 테이블을 자동으로 처리해주므로 도메인 모델이 단순해지고, 여러가지고 편리하다. 하지만 이 매핑을 실무에서 사용하기에는 한계가 있다.
회원과 상품 사이에 다대다 관계를 맺으면, 회원아이디와 상품 아이디만 담기게 된다. 하지만 보통은 이 외에도 주문한 날짜, 수량과 같은 컬럼이 더 필요하다. 이런 컬럼을 추가하면 @ManyToMany를 사용할 수 없다.
결국, 중간 테이블을 만들고, 거기에 칼럼을 추가해서, 일대다 / 다대일 관계로 풀어야 한다.

단방향, 양방향

데이터베이스 테이블은 외래 키 하나로 양 쪽 테이블 조인이 가능하기때문에, 데이터베이스는 단방향이니 양방향이니 나눌 필요가 없다.
그러나 객체는 참조용 필드가 있는 객체만 다른 객체를 참조하는 것이 가능하다.
그렇기 때문에 두 객체 사이에 하나의 객체만 참조용 필드를 갖고 참조하면 단방향 관계,
두 객체 모두가 각각 참조용 필드를 갖고 참조하면 양방향 관계라고 한다.

그럼 항상 양방향 관계면 쉽지 않을까?

객체 입장에서 양방향 매핑을 했을 때, 오히려 복잡해질 수 있다.
제일 좋은 방법은 기본적으로 단방향 매핑으로 하고, 나중에 역방향으로 객체 탐색이 꼭 필요하다고 느낄 때 추가하는 것이다.

연관관계의 주인

두 객체(A, B)가 양방향, 단방향 관계(A -> B, B -> A)를 맺을 때, 연관관계의 주인을 정해야 한다.
🙋‍♀️ 제어의 권한(외래 키를 비록한 테이블 레코드를 저장, 수정, 삭제 처리를 갖는) 실질적인 관계가 어떤 것인지 JPA에게 알려줘야한다. 연관 관계의 주인은 연관 관계를 갖는 두 객체 사이에서 조회, 저장, 수정, 삭제를 할 수 있지만, 연관 관계의 주인이 아니면 조회만 가능하다.

  • 외래 키가 있는 곳을 연관 관계의 주인으로 정하면 된다.
  • 양방향 관계는 항상 서로를 참조해야 한다. 연관관계 편의 메소드를 작성하는 것이 좋다. 편의 메소드는 한 곳에만 작성하거나 양쪽 다 작성할 수 있는데, 양쪽에 다 작성하면 무한루프에 빠지므로 주의해야 한다.
  • 연관관계 주인이 아니면 mappedBy 속성을 사용하고, 연관 관계 주인 필드 이름을 값으로 입력해야 한다.

연관관계 편의 메소드

Team team1 = new Team("Team1", "민정팀");
em.persist(team1);

Member member1 = new Member("Member1","민정");
member1.setTeam(team1); // 연관관계 설정 member1 -> team1
team1.getMembers().add(member1) // 연관관계 설정 tema1 -> member1

이렇듯 양방향 연관관계는 결국 양쪽 모두를 신경써야한다. 만약, setTeam과 getMembers().add를 각각 호출하면 실수가 발생할 수 있다. 따라서 양쪽 모두의 관계를 맺어주는 것하나의 코드처럼 사용하는 것이 안전하다.

public void setTeam(Team team) {
    this.team = team;
    team.getMembers().add(this);
}

하지만 위와 같이 setTeam 메서드를 작성하는 경우 버그가 발생할 수 있다. 예를 들어서

member1.setTeam(team1);
member1.setTeam(team2);

위와 같이 연속적으로 setTeam을 호출한 이후 team1에서 멤버를 조회하면 member1가 여전히 조회된다. team2로 변경할 때 team1과의 관계를 제거하지 않았기 때문이다.

public void setTeam(Team team) {
    if (this.team != null) { // 기존에 이미 팀이 존재한다면
        this.team.getMembers().remove(this); // 관계를 끊는다.
    }
    this.team = team;
    team.getMembers().add(this);
}

따라서 위와 같이 기존 팀과의 관계를 제거하는 코드를 추가해야 정상적으로 동작한다.

References

profile
기록을 통한 성장을

0개의 댓글