
JPA로 연관관계를 설계하다 보면 가장 많이 마주치는 구조 중 하나가 1:N 관계이다.
대표적으로 "회원 - 주문", "게시글 - 댓글", "카테고리 - 상품" 등이 이에 해당한다.
처음 연관관계를 설계할 때는 단순히 양방향으로 잡으면 된다고 생각했다….ㅋ(그냥 진짜 엔티티마다 연관관계 설정을 해줘야되는 줄..) 실제로는 방향을 어떻게 잡느냐에 따라 성능이나 코드 복잡도에 영향을 줄 수 있다고 한다.
그러면 여기서 이제 방향을 잡아줘야 되는데.. 나는 @OneToMany와 @ManyToOne 둘 중에 뭘 선택해야되는지 고민이 많이 됐다 ㅇㅅㅇ…;
이번 글에서는 1:N 관계에서 @OneToMany와 @ManyToOne 중 어느 방향을 설정하는 것이 더 나은 선택인지 정리해보고자 한다.
먼저 회원(Member)과 주문(Order)의 관계를 예시로 들어보자.
@OneToMany 단방향@Entity
public class Member {
@Id
private Long id;
@OneToMany
private List<Order> orders = new ArrayList<>();
}
처음에는 이렇게 "회원이 주문을 여러 개 갖는다"는 생각에 따라 Member 쪽에서 @OneToMany를 선언하는 방식을 사용했다. 하지만 이 방식은 추천되지 않는다고 하는데… 그 이유는!
위 코드는 주문(Order) 테이블에 외래 키(member_id)가 생성되지 않는다.
왜냐하면, 연관관계의 주인이 아닌 Member 엔티티에서는 @JoinColumn 없이 mappedBy도 없기 때문에 JPA가 외래 키를 어디에 둬야 하는지 알지 못한다.
결국 양방향 연관관계를 걸지 않는 한 제대로 된 매핑이 되지 않으며, 쿼리도 생각처럼 나오지 않는다고 한다..
@OneToMany 단방향을 사용하면, JPA는 연관관계를 제대로 알 수 없기 때문에 데이터를 저장할 때 다음과 같은 흐름으로 처리한다.
Member를 저장한다.Order들을 저장하는데, 이때는 member_id 값 없이 저장된다.Order에 대해 UPDATE 쿼리를 따로 실행해서 member_id를 설정한다.즉, 한 번에 끝낼 수 있는 작업을 INSERT와 UPDATE로 나눠서 처리하게 되므로, 불필요한 쿼리가 추가로 발생하고 성능이 떨어질 수 있다.
@ManyToOne가장 일반적이고 안정적인 방식은 N 쪽에서 연관관계를 설정하는 것, 즉 Order에서 @ManyToOne으로 Member를 참조하는 방식이다.
@Entity
public class Order {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
}
이 방식의 장점은 다음과 같다.
@JoinColumn을 통해 외래 키가 어떤 컬럼인지 명확히 설정할 수 있다.
DB 설계 관점에서도 외래 키는 항상 다(N) 쪽에 존재하는 것이 일반적이다.
Order를 조회하면 그에 연결된 Member를 쉽게 가져올 수 있다.
즉, 대부분의 실제 비즈니스 로직에서 "주문이 누구의 것인지"를 조회하는 경우가 많기 때문에 N 쪽에서 주인으로 설정하는 것이 효율적이다.
양방향 연관관계를 설정할 때 사용할 수 있다.
@Entity
public class Member {
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
이 경우 mappedBy 속성을 통해 연관관계의 주인이 Order임을 명시하고,
Member.orders는 읽기 전용으로 사용할 수 있다.
즉, 연관관계를 설정하는 주체는 여전히 Order이지만, Member 쪽에서 데이터를 조회할 수 있도록 하는 보조적인 역할을 하는 것이다.
이런 방식은 조회 편의성을 위해 사용하는 것이지, 실제 연관관계를 설정하는 주체는 아니다.
1:N 관계에서 어떤 방향을 잡아야 할지 고민된다면, 다음 기준을 기억하면 도움이 된다.
@ManyToOne을 우선적으로 사용한다.mappedBy를 이용해 양방향으로 설정하되, 연관관계의 관리는 N 쪽에서만 하도록 한다.이러한 기준을 기반으로 설계하면, 쿼리의 예측이 쉬워지고, 불필요한 update 발생이나 복잡한 로직을 줄일 수 있다!