티스토리 뷰
소스 코드는 여기 있습니다. (commit hash: 6695e56)
> git clone https://github.com/lcalmsky/jpa > git checkout 6695e56
Overview
다양한 연관관계에 대해 알아봅니다.
연관관계 매핑시 고려해야할 사항이 3가지가 있습니다.
- 방향(단방향, 양방향)
- 연관관계의 주인
- 다중성
이중 두 가지는 이미 이전 포스팅에서 살펴보았으므로 다중성에 대해 알아보겠습니다.
다중성
사실 다중성도 이전 포스팅에서 다루긴했지만 다시 한번 개념을 정리해보겠습니다.
DB 관점에서의 다중성을 말합니다.
JPA는 객체를 테이블에 매핑하기 위해 다양한 애너테이션을 사용하는데 이 중 다중성에 해당하는 애너테이션은 다음과 같습니다.
- @ManyToOne: 다대일
- @OneToMany: 일대다
- @OneToOne: 일대일
- @ManyToMany: 다대다
다중성은 각각 자신의 속성에 대해 대칭성을 가집니다.
Member
와 Team
이 N:1
이라면 Team
과 Member
는 당연히 1:N
의 관계가 됩니다.
@ManyToOne
단방향
가장 많이 사용되는 연관관계로 한 쪽이 다른 한 쪽을 참조하는데 그 반대로는 참조를 허용하지 않습니다.
DB는 FK를 사용하기 때문에 방향과 상관없이 참조 가능합니다.
이전 포스팅에서 살펴보았던 Member
대 Team
의 관계로 예를 들 수 있습니다.
양방향
객체가 양쪽을 서로 참조할 필요가 있을 때 사용합니다.
@OneToMany
단방향
객체에서 일대다 단방향은 일에 해당하는 쪽이 연관관계의 주인이 됩니다. 테이블에서는 다 쪽에 FK
가 존재합니다.
객체와 테이블의 차이 때문에 발생하는 헷갈리는 구조인데 아예 반대로 생각하는 게 마음이 편합니다.
@JoinColumn
을 사용하지 않으면 자동으로 join
하기 위한 테이블을 생성하므로 되도록이면 사용하시는 것을 권장합니다.
일대다 단방향 매핑은 관리하는 FK
가 다른 테이블에 있는 것 뿐만 아니라 연관관계 관리를 위해 추가로 update
를 실행해야 한다는 단점이 있습니다.
따라서 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하는 것이 개발에 좀 더 용이합니다.
실제로 Team
과 Member
가 있을 때 Team
위주로 개발을 해야할 일은 많지 않기 때문에 설명만 보면 어렵지만 실제 개발해야하는 상황에서는 직관적으로 선택을 할 수 있습니다.
양방향
이론적으로는 존재하지만 JPA
내에서 공식적으로 지원하지도 않기 때문에 간단한 설명으로 대체하겠습니다.
@JoinColumn(insertable=false, updatable=false)
이런식으로 설정하여 읽기 전용 방식으로 양방향 처럼 사용하는 방식입니다.
다대일 양방향 매핑을 사용하는 것으로 대체해야 합니다.
@OneToOne
일대일 관계는 그 반대도 동일하게 일대일이고 관계의 주인을 정할 때도 FK
를 누가 가지고있느냐에따라 달라집니다.
주 테이블에 FK
가 존재하는 경우 주 객체가 대상 객체의 참조를 가지는 것과 동일하게 사용할 수 있으므로 객체지향 개발자가 선호하는 방식이라고 할 수 있습니다.
JPA
매핑도 편리하고 직관적으로 할 수 있고, 주 테이블만 조회해도 대상 테이블에 데이터가 존재하는지 여부를 확인할 수 있습니다.
대신 값이 없는 경우 FK
에 null
을 허용해줘야 하는데 이 부분이 헷갈릴 수 있습니다.
대상 테이블에 FK
가 있는 경우 DBA
입장에서 조금 더 편리할 수 있습니다. 테이블의 관계가 변경되어야 하는 경우 좀 더 유연하게 대처할 수 있기 때문입니다.
반면 지연로딩으로 설정하더라도 즉시 로딩된다는 단점이 있는데 이 부분은 프록시를 다루는 부분에서 설명하도록 하겠습니다.
단방향
먼저 주 테이블(member)이 FK
를 가지고있는 경우 객체 관계는 아래 처럼 나타낼 수 있습니다.
다대일에서 단방향 매핑과 동일합니다.
반대로 대상 테이블(seat)이 FK
를 가지고있는 경우는 JPA
에서 지원하지 않습니다.
양방향
다대일 양방향 매핑 처럼 FK
를 가진쪽이 연관관계의 주인이 되고 반대는 mappedBy
를 사용합니다.
반대 테이블이 FK를 가지고 있을 때는 기존과 방법은 같고 객체에서 속성만 반대로 가지면 됩니다.
@ManyToMany
객체간은 서로를 참조할 수 있지만 이번엔 데이터베이스에서 두 개의 테이블 만으로 구현할 수 없는 방식이라고 할 수 있습니다.
그래서 FK만 매핑하는 테이블을 따로 생성해주는데 이 때 @JoinTable을 이용합니다.
객체는 컬렉션을 이용해 서로를 참조할 수 있어 두 개로 표현할 수 있습니다.
단방향, 양방향 매핑이 모두 가능합니다.
다대다 매핑이 언뜻 보기에는 좋아보일 수 있지만 중간 매핑 테이블에 컬럼을 추가해야할 경우가 생길 수 있습니다.
그럴 땐 매핑테이블을 직접 Entity
로 정의하여 사용할 수 있습니다.
이 경우 @OneToMany
- 매핑테이블
- @ManyToOne
을 사용하여 ManyToMany 관계를 만들 수 있습니다.
연관관계 매핑 예제
이전 포스팅에서 사용했던 스키마에 배송, 카테고리 테이블을 추가해보겠습니다.
주문과 배송은 일대일 관계를, 상품과 카테고리는 다대다 관계를 가집니다.
이것을 다시 객체로 나타내면 아래와 같습니다.
추가된 객체간의 관계는 모두 양방향 매핑으로 진행해보겠습니다.
Member
와 OrderItem
은 변경된 부분이 없으므로 변경되거나 추가된 클래스만 작성하겠습니다.
이전 포스팅을 참고해주세요.
/src/main/java/com/tistory/jaimenote/jpa/domain/entity/Order.java
package com.tistory.jaimenote.jpa.domain.entity;
import com.tistory.jaimenote.jpa.domain.support.OrderStatus;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.ToString.Exclude;
@Entity
@Table(name = "orders")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
public class Order {
@Id
@GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
@OneToMany(mappedBy = "order")
@Exclude
private List<OrderItem> orderItems = new ArrayList<>();
private LocalDateTime orderDateTime;
@Enumerated(EnumType.STRING)
private OrderStatus status;
@OneToOne
@JoinColumn(name = "delivery_id")
private Delivery delivery;
}
/src/main/java/com/tistory/jaimenote/jpa/domain/entity/Item.java
package com.tistory.jaimenote.jpa.domain.entity;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.ToString.Exclude;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
public class Item {
@Id
@GeneratedValue
@Column(name = "item_id")
private Long id;
private String name;
private Integer price;
private Integer stockQuantity;
@ManyToMany(mappedBy = "items")
@Exclude
private List<Category> categories = new ArrayList<>();
}
/src/main/java/com/tistory/jaimenote/jpa/domain/entity/Delivery.java
package com.tistory.jaimenote.jpa.domain.entity;
import com.tistory.jaimenote.jpa.domain.support.DeliveryStatus;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
public class Delivery {
@Id
@GeneratedValue
private Long id;
private String city;
private String street;
private String zipcode;
@Enumerated(EnumType.STRING)
private DeliveryStatus deliveryStatus;
@OneToOne(mappedBy = "delivery")
private Order order;
}
/src/main/java/com/tistory/jaimenote/jpa/domain/entity/Category.java
package com.tistory.jaimenote.jpa.domain.entity;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.ToString.Exclude;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
public class Category {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "category_parent_id")
private Category parent;
@OneToMany(mappedBy = "parent")
@Exclude
private List<Category> child = new ArrayList<>();
@ManyToMany
@JoinTable(
name = "category_item",
joinColumns = @JoinColumn(name = "category_id"),
inverseJoinColumns = @JoinColumn(name = "item_id")
)
@Exclude
private List<Item> items = new ArrayList<>();
}
DeliveryStatus
나 OrderStatus
는 enum
타입으로 껍데기만 생성했습니다.
마지막으로 애플리케이션을 실행해서 로그를 확인해보겠습니다.
2022-07-01 01:46:44.812 DEBUG 45574 --- [ main] org.hibernate.SQL :
create table category (
id bigint not null,
name varchar(255),
category_parent_id bigint,
primary key (id)
)
2022-07-01 01:46:44.816 DEBUG 45574 --- [ main] org.hibernate.SQL :
create table category_item (
category_id bigint not null,
item_id bigint not null
)
2022-07-01 01:46:44.817 DEBUG 45574 --- [ main] org.hibernate.SQL :
create table delivery (
id bigint not null,
city varchar(255),
delivery_status varchar(255),
street varchar(255),
zipcode varchar(255),
primary key (id)
)
2022-07-01 01:46:44.819 DEBUG 45574 --- [ main] org.hibernate.SQL :
create table item (
item_id bigint not null,
name varchar(255),
price integer,
stock_quantity integer,
primary key (item_id)
)
2022-07-01 01:46:44.820 DEBUG 45574 --- [ main] org.hibernate.SQL :
create table member (
member_id bigint not null,
city varchar(255),
name varchar(255),
street varchar(255),
zipcode varchar(255),
primary key (member_id)
)
2022-07-01 01:46:44.821 DEBUG 45574 --- [ main] org.hibernate.SQL :
create table order_item (
order_item_id bigint not null,
count integer,
order_price integer,
item_id bigint,
order_id bigint,
primary key (order_item_id)
)
2022-07-01 01:46:44.822 DEBUG 45574 --- [ main] org.hibernate.SQL :
create table orders (
order_id bigint not null,
order_date_time timestamp,
status varchar(255),
delivery_id bigint,
member_id bigint,
primary key (order_id)
)
2022-07-01 01:46:44.823 DEBUG 45574 --- [ main] org.hibernate.SQL :
alter table category
add constraint FKgbowg38afm73793kwnokn0203
foreign key (category_parent_id)
references category
2022-07-01 01:46:44.833 DEBUG 45574 --- [ main] org.hibernate.SQL :
alter table category_item
add constraint FKu8b4lwqutcdq3363gf6mlujq
foreign key (item_id)
references item
2022-07-01 01:46:44.834 DEBUG 45574 --- [ main] org.hibernate.SQL :
alter table category_item
add constraint FKcq2n0opf5shyh84ex1fhukcbh
foreign key (category_id)
references category
2022-07-01 01:46:44.837 DEBUG 45574 --- [ main] org.hibernate.SQL :
alter table order_item
add constraint FKija6hjjiit8dprnmvtvgdp6ru
foreign key (item_id)
references item
2022-07-01 01:46:44.838 DEBUG 45574 --- [ main] org.hibernate.SQL :
alter table order_item
add constraint FKt4dc2r9nbvbujrljv3e23iibt
foreign key (order_id)
references orders
2022-07-01 01:46:44.839 DEBUG 45574 --- [ main] org.hibernate.SQL :
alter table orders
add constraint FKtkrur7wg4d8ax0pwgo0vmy20c
foreign key (delivery_id)
references delivery
2022-07-01 01:46:44.842 DEBUG 45574 --- [ main] org.hibernate.SQL :
alter table orders
add constraint FKpktxwhj3x9m4gth5ff6bkqgeb
foreign key (member_id)
references member
의도한대로 category_item
테이블이 Entity
구현 없이 자동으로 생성되었고 FK
도 정확하게 추가된 것을 확인할 수 있습니다.
'JPA' 카테고리의 다른 글
[JPA] 상속관계 매핑(2): @MappedSuperclass (0) | 2022.07.06 |
---|---|
[JPA] 상속관계 매핑(1): 상속 전략 (1) | 2022.07.05 |
[JPA] 연관관계 매핑 예제 (0) | 2022.07.03 |
[JPA] 연관관계의 주인 (0) | 2022.06.30 |
[JPA] 양방향 연관관계 (0) | 2022.06.29 |
- Total
- Today
- Yesterday
- 스프링 부트 회원 가입
- 스프링 부트 애플리케이션
- 헥사고날 아키텍처
- Spring Boot
- 알고리즘
- Spring Data JPA
- 스프링 데이터 jpa
- Spring Boot JPA
- leetcode
- 함께 자라기 후기
- spring boot jwt
- proto3
- spring boot application
- @ManyToOne
- Jackson
- Linux
- 스프링 부트 튜토리얼
- Spring Boot Tutorial
- r
- spring boot app
- JSON
- 스프링부트
- Java
- 함께 자라기
- intellij
- 클린 아키텍처
- gRPC
- QueryDSL
- JPA
- 스프링 부트
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |