해당 글은 김영한 님의 ["자바 ORM 표준 JPA 프로그래밍"] 을 스터디 하면서 정리하는 글 입니다 !👨💻
이번 글에서는 양방향 연관관계 매핑시, 개발자에게 혼란을 줄 수 있는 연관관계 주인에 대해 살펴볼 것입니다. 개인적인 경험으로 보았을때, 연관관계 매핑 부분에서 가장 어려웠던 내용 중 하나였습니다❗️
차근차근 살펴보겠습니다 👨💻
연관관계 매핑시, 다양한 요소를 고려해야합니다.
이때 '빙향' 역시 고려 대상인데, 양방향이란 말 그대로 두 엔티티가 서로를 바라보고(참조하고) 있는 관계를 의미합니다.
해당 그림을 보면 주문-회원은 다대일 관계라는 것을 알 수 있습니다.
한명의 회원은 여러 주문을 할 수 있고, 하나의 주문에는 한명의 회원만 존재한다는 것을 의미합니다 ✅
// 회원 Entity
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "member_id")
private Long id;
private String name;
private String city;
private String street;
private String zipcode;
}
// 주문 Entity
@Entity
public class Order {
@Id @GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member; //주문 회원
private Long price;
}
기본적인 연관관계 매핑을 공부해보았더라면 해당 코드를 이해하는 것은 어렵지 않을 것 입니다.
주문 - 회원은 다대일 관계이며, 주문 테이블에서 회원 테이블을 참조하는 단방향 관계라는 것을 알 수 있습니다.
단뱡향 매핑만으로 테이블과 객체의 연관관계 매핑은 손쉽게 구현 할 수 있습니다 😎
자, 이번엔 방향을 양방향으로 구현해보겠습니다.
단방향과 양방향의 차이는 한쪽에서만 참조 할 것인지, 양쪽에서 참조 할 것인지에 차이에 있습니다.
// 회원 Entity
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "member_id")
private Long id;
private String name;
private String city;
private String street;
private String zipcode;
@OneToMany(mappedBy = "member")
private List<Order> orders; // 양방향 연관관계를 위해 추가 ❗️
}
// 주문 Entity
@Entity
public class Order {
@Id @GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member; //주문 회원
private Long price;
}
단뱡향과의 차이점이라면 Member Entity 필드에 Order Entity 필드를 참조 할 수 있는 컬렉션(Collection) 이 존재한다는 것입니다. @OneToMany 는 직관적으로 이해가 잘 될 것입니다.
그렇다면 'mappedBy' 속성은 어떤 역할일까요?
Entity 연관관계 매핑을 포함하는 Entity 매핑은 객체 와 테이블의 불일치성을 해소하면서 개발자로 하여금 객체지향적인 개발을 편리하게 가능하도록 하는 방법입니다.
Entity 매핑은 객체와 테이블의 불일치성을 해소한다.
그렇다면 둘 사이에 가장 큰 차이는 무엇일까요?
그것은 바로 객체의 참조(reference) 방식과 테이블의 조인(join) 방식입니다 ❗️
객체는 대상이 되는 다른 객체의 주소값을 통해 참조를 하는 방식입니다.
따라서 오로지 한쪽에서만 참조가 가능하며, 사실 완전히 양방향으로 참조할 수 있는 방식은 없습니다 ⛔️
그러나 설계상의 목적으로 양방향을 구현해야 한다면 애플리케이션 로직으로 잘 묶어서 단방향 연관관계 2개를 양방향인 것처럼 보이게 할 수 있습니다 🙆🏻
그러나 테이블은 대상이 되는 다른 테이블과 외래키를 통해 조인이라는 방식으로 얼마든지 양방향으로 관계를 맺을 수 있습니다.
객체는 단방향 연관관계만 가능하고, 테이블은 양방향 연관관계가 가능하다.
따라서 객체를 양방향으로 구현하기 위해서는 연관관계를 맺는 각 엔티티 클래스에 서로를 참조 할 수 있는 필드를 따로 두어야 하는데, 여기서 다시 문제가 발생합니다 👿
객체를 단방향으로 매핑하면 참조를 하나만 사용하므로 이 참조로 외래키를 관리하면 되지만, 양방향으로 매핑하게 되면 회원 -> 주문, 주문 -> 회원 두 곳에서 참조하게 됩니다. 따라서 연관관계를 관리하는 포인트는 2곳으로 늘어납니다 ❗️
따라서 객체의 참조는 둘인데 외래키는 하나인 결과를 불러일으킵니다 ❗️
그렇기 때문에 JPA 는 두 객체의 연관관계 중 하나를 정해서 테이블의 외래키를 관리해야 하는데 이것을 [연관관계의 주인]이라고 합니다.
여기서 또 하나의 궁금중이 생기겠죠?
어떤 쪽을 연관관계의 주인으로 설정해야 할까?..🥲
연관관계의 주인을 정한다는 것은 외래키 관리자를 선택하는 것이다.
데이터베이스 테이블에서는 항상 다 쪽이 외래 키를 가집니다
따라서 주문(다) - 회원(일) 양방향 관계에서는 주문 엔티티 클래스의 member 필드가 연관관계의 주인이며, 회원의 orders 필드는 연관관계의 주인이 아니기 때문에 mappedBy 속성을 사용하며 mappedBy 속성의 값은 연관관계의 주인이 되는 필드명을 사용합니다 ✅
결론적으로 이렇게 주문 - 회원 양방향 관계를 설정하게 되면 주문 쪽에서 회원 아이디(외래키)를 통해 회원 정보를 저장하게 되며, 회원 쪽에서는 자신의 주문 리스트를 읽기만 가능합니다 ✅