-
[JPA] 엔티티 연관관계 매핑 테스트 (1) : @OneToMany, @ManyToOneSpring/JPA 2023. 4. 23. 23:32
☑️ 글 작성 개요
최근 김영한님의 JPA 프로그래밍과 최범균님의 도메인 주도 개발 시작하기 (이하 DDD 시작하기), 두 책을 병행하면서 읽고 있다.
그러던 중, DDD 시작하기 책에서 객체지향의 사용성을 높이기 위한 ORM 사용으로 JPA를 활용하는 파트를 공부하게 되었다.
JPA 책에서는 @OneToMany 혹은 @ManyToOne와 같은 어노테이션을 사용하여 연관 엔티티와 매핑을 하고, 이를 데이터베이스 테이블에서는 어떻게 생성되는지 공부했다.
나는 @ManyToOne, @OneToMany를 활용한 컬렉션 형태의 매핑은 사용해본 경험이 없어서, 더 이해가 어려웠다.
그런데 DDD 시작하기에서 JPA의 사용 예제 중 하나로 컬렉션 데이터를 담는 용도로 @CollectionTable을 사용하여 데이터베이스 테이블을 생성하는 것을 보고 큰 혼란이 왔다. JPA책에서는 일대다 관계에서 OneToMany를 사용했었는데? 게다가 DDD 책에서 앞 페이지에서 밸류객체라고 명시되어 있었는데 갑자기 테이블이 어떻게 생성되는 설명이 나왔다.
이때부터 이해했다고 하는 것들에 혼란이 오고, 둘이 어떤 차이점이 있는지 머리로 이해가 가지 않아서 직접 코드를 작성해보기로 했다.
해당 포스팅에서는 @ManyToOne과 @OneToMany를 사용한 가벼운 엔티티 매핑을 직접 구현해보고 데이터베이스를 확인할 것이다.
@OneToMany, @CollectionTable에 대한 비교는 다음 포스팅에서 이루어진다.
모든 소스코드는 GitHub에 첨부해 두었습니다.
☑️ 추천 독자
JPA에 대한 지식은 있지만 @ManyToOne과 @OneToMany를 사용한 초간단 예제를 포스팅 하나로 확인해보고 싶은 JPA 사용자
아주 간단한 예제이기 때문에 매핑에 대한 복잡한 예시를 원하는 사용자에게는 추천하지 않는다.
☑️ 상황 설정
Order와 OrderLine의 관계를 세 가지 유형으로 구현해보고자 한다.
주문 - 주문에 포함된 상품과 갯수
차이를 한 눈에 보고싶어서 최소한의 컬럼만 사용했고, 어떤 방법으로 생성했는지 컬럼명만 보고 구분이 가능하도록 했다.
공통적인 컬럼은 아래 표와 같이 설정하였다.
☑️ 테스트 케이스
@OneToMany, @ManyToOne
위 어노테이션을 사용한 엔티티의 연관관계에서는 관계의 주인이 있다.
요약해서 말하면 @ManyToOne과 같은 어노테이션으로 서로 연관관계를 가지고 있는 엔티티를 저장할 때, 연관관계의 주인이 되는 엔티티에서는 연관 엔티티 관계를 저장할 수 있지만, 반대의 경우에서는 관계 매핑이 저장되지 않는다.
실제로 엔티티에서 저장을 시도해보는 테스트를 진행하기로 했다.
@ManyToOne @OneToMany case 1) 연관관계의 주인이 아닌 오더에서 연관관계 매핑하기 case 2) 연관관계의 주인인 오더라인에서 연관관계 매핑하기 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * 다음 포스팅 분리 * case 3) @Embedded case 4) @Embedded + @CollectionTable
@OneToMany + @ManyToOne
* 주의사항 : Table명 order는 생성되지 않아서 ( order_entity - order_line_entity )로 설정했다.
@Entity(name = "order_entity") @NoArgsConstructor public class OrderEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "order_id") Integer id; String name; @OneToMany(mappedBy = "order") List<OrderLineEntity> orders = new ArrayList<>(); // 생성자, 기타소스 }
@Entity @NoArgsConstructor public class OrderLineEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "order_line_id") Integer id; @ManyToOne @JoinColumn(name = "order_id") private OrderEntity order; Integer entityProductId; Integer entityQuantity; // 생성자, 기타소스 }
case 1) 연관관계의 주인이 아닌 오더에서 연관관계 매핑하기
해당 테스트 코드에서는 "엔티티 테스트"라는 이름으로 둘 사이의 연관관계를 Order 객체의 OrderLine 컬렉션에 추가하는 형태로 매핑하고 있다.
하지만 데이터베이스를 확인하면 다음과 같이 연관관계에 대한 정보가 존재하지 않는다. (order_line_entity의 order_id 가 null)
case 2) 연관관계의 주인인 오더라인에서 연관관계 매핑하기
해당 테스트 코드는 위와 연관관계를 매핑하는 부분을 제외하고는 동일하고 "엔티티 테스트2"라는 이름으로 저장하고 있다.
둘 사이의 연관관계를 OrderLine 객체의 Order 객체에 추가하는 형태로 매핑하고 있다.
실제 테이블 데이터는 다음과 같다.
Order에서 연관관계를 매핑한 첫번째 "엔티티 테스트"와 달리 OrderLine에서 연관관계를 매핑한 "엔티티 테스트2"는 orderLine 테이블에 외래키로 저장되어 있다.
+) 테스트 '엔티티_오더_테스트'에서는 해당 컬럼이 null이 허용되어서 테스트가 통과하지만, 이와 같이 nullable을 false로 조건을 추가하면 테스트 코드가 실패한다.
Entity를 사용하는 Order와 OrderLine에는 다음과 같은 형태로 데이터가 저장된다.
Order와 OrderLine을 엔티티로 연관관계 매핑을 하면
1) 각자 고유 Id를 가지고
2) 외래키를 이용하여 연관관계를 매핑하고
3) 독립된 테이블로 존재하기 때문에 꼭 둘 사이를 그래프 탐색으로 참조하지 않아도 된다.
이 포스팅이 복잡해서 이해가 되지 않는다면 아래 영상을 참고해볼 수 있다.
백기선님 JPA 영상 'Spring > JPA' 카테고리의 다른 글
[JPA] 언제 N+1이 발생할까? (0) 2023.06.22 [JPA] 엔티티 연관관계 매핑 테스트 (2) : @Embedded, @ElementCollection 사용해보기 (0) 2023.04.25 [JPA] 영속성에 대한 초간단 테스트 (0) 2023.04.06