Home JPA - 연관관계 매핑
Post
Cancel

JPA - 연관관계 매핑

연관관계가 필요한 이유

객체를 테이블에 맞추어 데이터 중심으로 모델링 하면, 협력관계를 만들 수 없음

  • 데이블은 외래키로 조인을 사용해서 연관된 테이블을 찾음
  • 객체는 참조를 사ㅇ해서 연관된 객체를 찾음
  • 데이블과 객체 사이에는 이런 큰 간격이 존재 함

단방향 연관관계

어느 한쪽만이 다른쪽을 소유하고 있는 관계. 객체의 참조와 테이블의 외래키를 매핑.

Alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Entity
public class Team {
  @Id
  @GeneratedValue
  @Column(name = "TEAM_ID")
  private Long id;

  private String name;
}

@Entity
public class Member {
  @Id
  @GeneratedValue
  @Column(name = "MEMBER_ID")
  private Long id;

  @Column(name = "USERNAME")
  private String name;

  // 객체의 참조와 테이블의 FK를 매핑
  @ManyToOne
  @JoinColumn(name = "TEAM_ID")
  private Team team;
}

Alt text

양방향 연관관계

어느 한쪽만이 다른쪽을 소유하고 있는 관계가 아닌 서로 참조하는 관계

Alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Entity
public class Team {

  @Id
  @GeneratedValue
  @Column(name = "TEAM_ID")
  private Long id;

  private String name;

  @OneToMany(mappedBy = "team")
  private List<Member> members = new ArrayList<>();
}

@Entity
public class Member {
  @Id
  @GeneratedValue
  @Column(name = "MEMBER_ID")
  private Long id;

  @Column(name = "USERNAME")
  private String name;

  @ManyToOne
  @JoinColumn(name = "TEAM_ID")
  private Team team;
}

...

//조회
Team findTeam = em.find(Team.class, team.getId());
int memberSize = findTeam.getMembers().size(); //역방향 조회

양방향 연관관계

객체와 테이블이 관계를 맺는 차이

테이블의 경우, 외래키로 두 테이블이 묶여 있을 떄 해당 외래키를 이용 하여 조인하면 값을 확인할 수 있음. 즉, 테이블은 방향이라는 개념이 따로 없으며 굳이 정하자면 외래키를 통하여 서로간 양방향 관계라고 볼 수 있음.

그러나 객체 참조의 경우 클래스의 필드에 타 클래스의 참조가 존재하지 않는한 참조관계가 성립되지 않고, 참조하고 있는 클래스만이 단방향으로 관계를 맺고 있음. 즉, 참조에서 양방향관계를 맺기 위해선 서로간 참조를 실제로 소유하고 있어야 함. 그러므로 양방향 관계라고 하더라도 사실상 단방향 관계가 두개가 있는 형상임

  • 객체가 양방향 연관관계 일때의 세부 연관관계 = 2개
    • 회원 -> 팀 연관관계 1개(단방향)
    • 팀 -> 회원 연관관계 1개(단방향)
  • 테이블 양방향 연관관계 일때의 세부 연관관계 = 1개
    • 회원 <-> 팀의 연관관계 1개(양방향)

Alt text

객체의 양방향 관계
  • 객체의 양방향 관계는 사싱 양방향 관계가 아니라 서로다른 2개의 단방향 관계의 조합 임
  • 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야함
1
2
3
4
5
6
7
class A {
  B b;
}

class B {
  A a;
}
테이블의 양방향 관계
  • 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
  • 외래키 하나로 인하여 양방향 연관관계를 가짐
1
2
3
4
5
6
7
8
9
-- MEMBER.TEAM_ID 외래키 하나로 양방향 연관관계를 가짐
-- (양쪽으로 조인할 수 있음)
SELECT *
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

SELECT *
FROM TEAM T
JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID

연관관계의 주인

결국 객체에선 양방향 관계에선 결국 2개의 단방향 관계가 성립되므로 어느 한쪽이 외래키를 매핑하여 관리할 것인지 지정해 주어야 함

Alt text

양방향 매핑 규칙
  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
  • 연관관예의 주인만이 외래 키를 관리(등록, 수정)
  • 주인이 아닌 쪽은 읽기만 가능
  • 주인은 mappedBy 속성을 사용하면 안됨
  • 주인이 아니면 mappedBy 속성을 사용하여 주인을 지정해야 함
주인 선정 기준
  • 외래키가 있는 곳을 주인으로 선택하는 방법이 좋음
  • 아래 그림의 예에선 Member.team이 연관관계의 주인으로 선정

Alt text

양방향 연관관계에서의 주의점

연관관계의 주인에 값을 입력하지 않는 경우
1
2
3
4
5
6
7
8
9
10
11
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");

//역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member);

em.persist(member);
양방향 매핑시 연관관계의 주인에 값을 입력해야함
1
2
3
4
5
6
7
8
9
10
11
12
13
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");

team.getMembers().add(member);

//연관관계의 주인에 값 설정
member.setTeam(team);

em.persist(member);
  • commit() 시점에 실제 쿼리가 적용되고 이전에는 영속성 컨텍스트의 1차 캐시에만 객체가 저장되어 있는 상태
  • 해당 상황에서 연관관계의 주인에만 값을 설정하게 되면 flush(), clear()와 같이 바로 쿼리를 적용하지 않는이상 하나의 트랜잭션 내에서 주인이 아닌 쪽에는 값이 설정되지 않은 상태가 됨
주의점 정리
  • 순수 객체 상태를 고려하여 항상 양쪽에 값을 설정해야 함
  • 연관관계 편의 메소드를 생성하도록 함
  • 양방향 매핑시에 무한루프를 조심해야 함
    • 예) toString(), lombok, JSON 생성 라이브러리

Controller 에서 Entity를 바로 반환하지 않도록 해야 함 Controller 에서 Entity를 바로 반환시 JSON 으로 변환하는 과정에서 무한루프에 빠질수 있으며, 무엇보다 Entity 수정시 API 도한 수정이 발생하여 정합을 다시 해야하는 문제가 발생함. 그러므로 되도록이면 Entity를 바로 반환하는 것이 아닌 단순 데이터가 있는 DTO로 변환하여 반환하는것이 좋음.

양방향 매핑 정리

  • 단방향 매핑만으로도 이미 연관관계 매핑을 완료
    • 되도록이면 양방향 매핑을 피하는 설계가 좋음
  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
  • JPQL에서 영방향으로 탐색할 일이 많음
  • 단방향 매핑을 잘 하고 양방향은 필요할 떄 추가해도 됨(테이블에 영향을 주지 않음)
  • 비지니스 로직을 기준으로 연관관계의 주인을 선택하는 것이 아닌 외래키의 위치를 기준으로 연관관계의 주인을 정해야 함

참고

  • 자바 ORM 표준 JPA 프로그래밍(김영한)
This post is licensed under CC BY 4.0 by the author.