[JPA] 연관관계 매핑(단방향, 양방향)
JPA는 엔티티라는 객체를 이용해서 데이터를 관리하고 관계형 데이터베이스는 테이블을 이용해 데이터를 관리한다.
관계가 없는 테이블은 컬럼과 엔티티의 필드를 매핑해서 작성하지만, 서로 관련 있는 엔티티들의 경우엔 관계형 DB에서 사용하는 외래키와 같은 연관관계를 매핑해주어야 한다.
엔티티를 관계형 DB의 테이블처럼 작성하면 다음과 같다.
@Getter
@Setter
@Entity
public class Member{
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long memberId;
private String name;
}
@Getter
@Setter
@Entity(name = "ORDERS")
public class Order {
@Id @GeneratedValue
@Column(name = "ORDER_ID")
private Long orderId;
@Column(name = "ITEM_NAME")
private String itemName;
@Column(name = "MEMBER_ID")
private Long memberId;
}
// 데이터 삽입
Member member = new Member();
member.setName("member");
em.persist(member);
Order order = new Order();
order.setItemName("itemA");
order.setMemberId(member.getMemberId());
em.persist(order);
//데이터 조회
Order findOrder = em.find(Order.class, order.getOrderId());
Member findMember = em.find(Member.class, findOrder.getMemberId());
String memberName = findMember.getName();
주문 데이터에서 주문한 사람의 이름을 찾기 위해서 Order 엔티티를 찾고 다시 Member 엔티티를 찾아야 한다.
JPA는 DB 중심적인 개발의 불편함을 줄이고 객체를 활용해서 개발하기 위함인데 위에 있는 코드는 전혀 그러지 못하고 있다.
테이블의 외래키 처럼 엔티티 간의 관계를 설정하는 것이 연관관계 매핑이다.
연관관계 매핑 - 단방향 연관관계
앞에서 본 상황을 연관관계로 매핑하면 다음처럼 작성할 수 있다.
@Getter
@Setter
@Entity(name = "ORDERS")
public class Order {
@Id @GeneratedValue
@Column(name = "ORDER_ID")
private Long orderId;
@Column(name = "ITEM_NAME")
private String itemName;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
}
Member member = new Member();
member.setName("member");
em.persist(member);
Order order = new Order();
order.setItemName("itemA");
order.setMember(member); //meberId대신 member 엔티티를 사용
em.persist(order);
//조회
Order findOrder = em.find(Order.class, order.getOrderId());
String memberName = findOrder.getMember().getName();
Order 클래스의 memberId에서 Member 클래스를 참조하게 변경하면 연관관계 매핑은 끝이다.
Order 기준으로 N:1 관계이므로 @ManyToOne, @JoinColumn을 사용해서 지정해주면 된다.
@JoinColumn의 name 속성은 해당 엔티티의 테이블 컬럼 명을 지정해주는 애노테이션이다.
조회도 Order 엔티티만 조회해서 Member를 참조 형식으로 찾는다.
이렇게 Order 엔티티에서 Member 엔티티를 참조하게 매핑하는 것을 단방향 연관관계 매핑이라고 한다.
연관관계 매핑 - 양방향 연관관계
DB는 FK 하나로 연관된 테이블을 찾을 수 있다. 하지만 JPA에선 양쪽 엔티티에 연관관계를 설정해줘야 양쪽에서 참조가 가능하다.
Order 엔티티에서 Member 엔티티를 참조하게 해서 Order를 통해 Member의 데이터를 가져올 수 있었지만, Member에서 Order는 참조하지 못했다.
Member에서 Order를 참조하기 위해서는 연관관계를 하나 더 설정해 주어야 한다. 즉, 양방향 연관관계 매핑은 단방향 연관관계 매핑을 양쪽에서 맺어주는 것이다.
@Getter
@Setter
@Entity
public class Member{
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long memberId;
private String name;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
@Getter
@Setter
@Entity(name = "ORDERS")
public class Order {
@Id @GeneratedValue
@Column(name = "ORDER_ID")
private Long orderId;
@Column(name = "ITEM_NAME")
private String itemName;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
}
Order 입장에선 다대일 단방향이었으므로 Member 엔티티 클래스에서는 일대다인 @OneToMany를 사용한다. 이때 mappedBy 속성으로 Order 클래스에서 참조하고 있는 Member의 필드 명인 member를 입력한다.
Member member = new Member();
member.setName("member");
em.persist(member);
Order order = new Order();
order.setItemName("itemA");
order.setMember(member);
em.persist(order);
em.flush();
em.clear();
Member findMember = em.find(Member.class, member.getMemberId());
for (Order o : findMember.getOrders()) {
System.out.println("itemName = " + o.getItemName());
양방향으로 연관관계를 맺어줌으로써 Member를 통해 Order를 참조할 수 있게 되었다.
양방향 연관관계 매핑 규칙
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정한다.
- 연관관계의 주인만 외래키를 관리한다. (등록, 수정)
- 주인이 아닌 쪽은 읽기만 가능하다.
- 일대다 관계에서 '다' (Order)를 연관관계 주인으로 지정한다.
- 양방향 연관관계 매핑 주의점
Order 엔티티가 아닌 Member 엔티티에서 Order를 추가하면 Orders 테이블의 member_id는 저장되지 않는다.
양방향 매핑 관계에서는 연관관계 주인에 값을 저장해야 한다.
Order order = new Order();
order.setItemName("itemA");
em.persist(order);
Member member = new Member();
member.setName("member");
member.getOrders().add(order);
em.persist(member);
/*
Hibernate:
/* insert hellojpa.Order
*/ insert
into
ORDERS
(ITEM_NAME, MEMBER_ID, ORDER_ID)
values
(?, ?, ?)
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, MEMBER_ID)
values
(?, ?)
*/
JPA의 관점에서 보면 연관관계 주인 엔티티에만 값을 저장해주면 되지만, 객체지향적인 관점에서 보면 양쪽에 다 값을 넣어주는 것이 맞다. addMember 같은 메서드를 통해 양방향 연관관계 편의 메서드를 만들어서 사용하자
@Getter
@Setter
@Entity(name = "ORDERS")
public class Order {
//..
public void addMember(Member member){
this.member = member;
member.getOrders().add(this);
}
}
편의 메서드는 Member나 Order 어느 곳이든 상관없지만 한쪽에만 만들고 양쪽 다 사용하지 않는 것이 좋다.
참고 자료 : 김영한님 자바 ORM 표준 JPA 프로그래밍 - 기본편
'Programming > JPA' 카테고리의 다른 글
[JPA] 영속성 컨텍스트 (Persistence Context) (0) | 2022.11.08 |
---|