반응형
연관관계 매핑
연관관계 매핑이란?
- 대부분의 엔티티는 다른 엔티티와 연관 관계를 가진다.
- 객체는 '참조'를 통해 연관관계를 맺는다. 예를 들어, 연관된 데이터를 조회할 때 Order객체는 필드의 Member객체를 참조(order.getMember())한다.
- 이를 객체 그래프 탐색이라 한다.
- 반면, 테이블은 '외래 키'를 통해 연관관계를 맺는다. 예를 들어, 연관된 데이터를 조회할 때 order 테이블은 member_id라는 외래 키를 통해 member테이블을 참조(SELECT * FROM ORDER O JOIN MEMBER M ON O.MEMBER_ID = M.ID)한다.
- 이를 조인이라 한다.
- 이렇게, 객체의 연관관계와 테이블의 연관관계는 차이를 보인다. 이 둘을 매핑하는 것을 연관관계 매핑이라 한다.
- 연관관계 매핑을 이해하기 위한 핵심 키워드는 다음의 세 가지가 있다.
- 방향 : 양방향 매핑과 단방향 매핑이 있다. 객체의 경우에는 둘 모두 존재하고, 테이블의 경우에는 양방향만 존재한다.
- 다중성 : 연관관계는 일대일부터 일대다, 다대일, 다대다의 다중성이 있다.
- 연관관계의 주인 : 객체를 양방향 연관관계로 만들면 연관관계의 주인을 정해야한다. 이후는 후술한다.
단방향 연관관계
- 연관관계 중에선 다대일 단방향 관계를 가장 먼저 이해해야 한다. 다음의 관계를 통해 다대일 단방향 관계를 알아보자.
- 회원과 팀이 있다.
- 회원은 하나의 팀에만 소속될 수 있다.
- 따라서, 회원과 팀은 다대일 관계다.
- 객체 연관관계
- 회원 객체는 Member.team 필드로 팀 객체와 연관관계를 맺는다.
- 회원 객체와 팀 객체는 단방향 관계다. 회원은 Member.team을 통해 팀을 알 수 있지만, 팀은 회원을 알 수 없다.
- 테이블 연관관계
- 회원 테이블은 TEAM_ID 외래 키로 팀 테이블과 연관관계를 맺는다.
- 회원 테이블과 팀 테이블은 양방향 관계다. 회원 테이블의 TEAM_ID 외래 키를 통해서 회원과 팀을 조인할 수도 있고, 팀과 회원을 조인할 수도 있다.
- 객체 연관관계와 테이블 연관관계의 가장 큰 차이
- 참조를 통한 연관관계는 언제나 단방향이다. 객체 간 연관관계를 양방향으로 만들고 싶으면, 반대쪽에도 필드를 추가해 참조를 보완해야 한다.
- 즉, 엄연히 이야기하면 객체간 양방향 연관관계는 서로 다른 두 개의 단방향 연관관계다.
- 반면, 테이블은 하나의 외래 키로 양방향 조인이 가능하다. 즉, 외래 키를 통한 연관관계는 언제나 양방향이다.
연관관계 사용
- 저장
public void save(){
Member member = new Member(1, "회원 1");
em.persist(member);
Order order = new Order(1, "주문 1");
order.setMember(member);
em.persist(order);
}
- 주문 엔티티는 회원 엔티티를 참조하고 저장했다. JPA는 참조한 회원의 식별자를 외래 키로 사용해서 적절한 등록 쿼리를 생성한다.
- JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 함을 명시하자.
- 위에서는 회원 엔티티를 영속화하고 사용했다.
- 조회
- 연관관계가 있는 엔티티는 참조를 통해 조회하는 방법(객체 그래프 탐색)과 JPQL을 이용하는 방법을 통해 조회 가능하다.
- 객체 그래프 탐색
- order.getMember();
- JPQL
- select o from Order o join o.member m where m.id = :id
- 수정
- 저장과 같은 방법을 이용한다.
- 값이 변경되면서 트랜잭션 커밋 시 플러시가 일어나면서 변경 감지 기능이 작동하며 UPDATE 쿼리가 실행된다.
- 삭제
- 수정과 같은 방법을 이용하나, 수정자를 통해 연관객체를 null 처리한다.
- 마찬가지로 변경 감지 기능이 작동하며 UPDATE쿼리가 실행된다. 외래 키 값에 NULL이 들어간다.
- 연관된 엔티티 삭제
- 연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거하고 삭제해야 한다. 그렇지 않으면 외래 키 제약 조건으로 인해 데이터베이스에서 오류가 발생한다.
- 위의 예시에서는, 미리 order의 member를 null처리 해 연관관계를 삭제하고, member를 삭제해야 한다.
양방향 연관관계
- 테이블은 기본적으로 양방향 연관관계이다. 따라서, 데이터베이스에는 단방향 연관관계에 별도로 추가할 필요는 없다.
- 그러나 객체의 경우, 반대쪽에서도 알고 있어야하기 때문에, 반대쪽에도 참조 객체를 추가해주어야 한다.
- 다시 말하지만, 객체에서의 양방향 연관관계는 단방향 두 개를 양방향처럼 사용할 뿐이다. 따라서, 단방향과 크게 차이는 없다.
- 다만, 연관관계의 주인을 잘 정해야한다.
연관관계의 주인
- 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리한다.
- 그러나 엔티티를 양방향으로 매핑하면 A -> B / B -> A 두 곳에서 서로를 참조한다. 결국, 객체의 연관관계를 관리하는 포인트가 두 곳으로 늘어난다. 여기서 둘의 차이가 발생한다.
- 따라서, 두 객체 연관관계 중 하나를 정해서 테이블의 외래 키를 관리해야 한다.
- 이를 연관관계의 주인이라 한다. 연관관계의 주인은 mappedBy 를 통해 지정한다.
- 연관관계의 주인은 외래 키를 관리한다. 외래 키를 '관리'한다는 것은 외래 키를 '가지고있다'는 것이다. 즉, 외래 키가 있는 객체를 연관관계의 주인으로 지정해야 한다.
public class Order {
@ManyToOne
Member member;
}
//////////////////////////////////////////
public class Member {
@OneToMany(mappedBy = "member") // 연관관계의 주인은 Order.member
List<Order> orders = new ArrayList<>();
}
- 쉽게 생각하면, 1 : N의 관계에서 1에 해당하는 객체가 연관관계의 주인이 된다. 만일 Order가 Member를 rebmem이라고 저장했으면 mappedBy = "rebmem"이 됐을 것이다.
양방향 연관관계 주의점
- 양방향 연관관계 저장
- 기본적으로는 단방향 연관관계와 같다.
- 위에서 사용했던 save()메서드를 그대로 사용해도 데이터베이스에는 두 데이터의 값이 잘 들어간다.
- 순수한 객체까지 고려한 양방향 연관관계
- 그러나 객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 안전하다.
- 양쪽 방향 모두 값을 입력하지 않으면 JPA를 사용하지 않는 순수한 객체 상태에서 심각한 문제가 발생할 수 있다.
- List에 값을 넣어주지 않으면, 당연히 List는 비어있기 마련이다.
- 따라서, 객체의 양방향 연관관계는 양쪽 모두 관계를 맺어주자.
- 연관관계 편의 메서드
public class Order {
@ManyToOne
Member member;
public void setMember(Member member){
if(this.member != null){
this.member.getOrders().remove(this);
}
this.member = member;
member.getOrders().add(this);
}
}
- 위와 같이 Order#setMember 메서드를 만들어 놓으면 order.setMember(member); 를 통해 쉽게 양방향 설정을 할 수 있다.
- 이 때 기존에 연관관계가 맺어져있었다면, 이를 반드시 먼저 삭제하고 새롭게 연관관계를 맺어주어야 한다.
정리
- 단방향 매핑과 비교했을 때, 양방향 매핑은 복잡하다. 연관관계의 주인도 정해줘야 하고, 양방향 연관관계를 만들기 위해서는 로직도 꼼꼼히 신경써야한다.
- 단방향에 비해 양방향이 지닌 장점은 객체 그래프 탐색 기능이 추가되어 탐색이 쉽다는 점 뿐이다.
- 따라서, 일단 단방향 연관관계를 맺어놓고, 반대쪽에서도 탐색이 필요하다면 그 때 양방향으로 맺어주자.
반응형
'Programming > JPA 프로그래밍' 카테고리의 다른 글
07. 고급 매핑 (0) | 2023.11.13 |
---|---|
06. 다양한 연관관계 매핑 (1) | 2023.11.06 |
03. 영속성 관리 (0) | 2023.08.18 |
01. JPA 소개 (0) | 2023.08.15 |
0장. 들어가며 (0) | 2023.08.07 |
댓글