9. 연관관계 매핑
RDBMS를 사용할 때 테이블 하나만으로 애플리케이션의 모든 기능을 구현하기란 불가능하다.
대체로 설계가 복잡해지면 각 도메인에 맞는 테이블을 설계하고 연관관계를 설정해서 조인(Join)등의 기능을 활용한다.
JPA를 사용하는 애플리케이션에서도 테이블의 연관관계를 엔티티 간의 연관관계로 표현할 수 있다.
9.1 연관관계 매핑 종류와 방향
- 연관관계를 맺는 두 엔티티 간에 생성할 수 있는 연관관계의 종류.
- One To One: 일대일(1:1)
- One To Many: 일대다(1:N)
- Many To One: 다대일(N:1)
- Many To Many: 다대다(N:M)
1) 연관관계를 이해하기 위해 한 가게가 재고관리시스템을 통해 상품을 관리하고 있다고 가정한다.
2) 재고로 등록돼 있는 상품 엔티티에는 가게로 상품을 공급하는 공급업체의 정보 엔티티가 매핑돼 있다.
3) 공급업체 입장에서 보면 한 가게에 납품하는 상품이 여러 개 있을 수 있으므로 상품 엔티티와는 일대다 관계가 되며, 상품 입장에서 보면 하나의 공급업체에 속하게 되므로 다대일 관계가 된다.
4) JPA를 사용하는 객체지향 모델링에서는 엔티티 간 참조 방향을 설정할 수 있다.
5) 데이터베이스와 관계를 일치시키기 위해 양방향으로 설정해도 무관하지만 비즈니스 로직의 관점에서 봤을 때는 단방향 관계만 설정해도 해결되는 경우가 많다.
- 단방향 : 두 엔티티 관계에서 한쪽의 엔티티만 참조하는 형식
- 양방향 : 두 엔티티의 관계에서 각 엔티티가 서로의 엔티티를 참조하는 형식
6) 연관관계가 설정되면 한 테이블에서 다른 테이블의 기본값을 외래키로 갖게 된다.
7) 이런 관계에서는 주인(Owner)이라는 개념이 사용된다.
8) 일반적으로 외래키를 가진 테이블이 그 관계의 주인이 되며, 주인은 외래키를 사용할 수 있으나 상대 엔티티는 읽는 작업만 수행할 수 있다.
9.2 프로젝트 생성
스프링 부트 핵심 가이드 4장 [스프링 부트 애플리케이션 개발하기]
4장 스프링 부트 애플리케이션 개발하기 4.1 프로젝트 생성 4.1.1 인텔리제이 IDEA에서 프로젝트 생성하기 IntelliJ IDEA Ultimate 버전에 내장된 Spring Initializr를 사용하여 외부에서 프로젝트를 생성할 필
yoonhs98.tistory.com
9.3 일대일 매핑
하나의 상품에 하나의 상품정보만 매핑되는 구조는 일대일 관계이다.
9.3.1 일대일 단방향 매핑
- 상품정보 엔티티
@Entity
@Table(name = "product_detail")
@Getter
@Setter
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ProviderDetail extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;
@OneToOne
@JoinColumn(name = "product_detail")
private Provider product;
}
- @JoinColumn 어노테이션을 사용해 매핑할 외래키를 설정한다.
- name 속성을 사용해 외래키의 이름을 설정한다.
- referencedColumnName: 외래키가 참조할 상대 테이블의 칼럼명을 지정한다.
- OneToOne 어노테이션은 다른 엔티티 객체를 필드로 정의했을 때 일대일 연관관계로 매핑하기 위해 사용한다.
- null 값을 허용하지 않으려면 'optional = false' 속성을 사용하여 null값을 방지한다.
9.3.2 일대일 양방향 매핑
- 일대일 양방향 매핑을 위한 Product 엔티티 (양쪽에서 단방향으로 서로를 매핑)
@Entity
@Table(name = "product_detail")
@Getter
@Setter
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ProviderDetail extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long number;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Integer price;
@Column(nullable = false)
private Integer stock;
@OneToOne
private ProviderDetail providerDetail;
}
9.4 다대일, 일대다 매핑
하나의 상품에 하나의 상품정보만 매핑되는 구조는 일대일 관계이다.
9.4.1 다대일 단방향 매핑
- 공급업체 엔티티 클래스
@Entity
@Table(name = "product_detail")
@Getter
@Setter
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ProviderDetail extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
- 상품 엔티티와 공급업체 엔티티의 다대일 연관관계 설정
@Entity
@Table(name = "product_detail")
@Getter
@Setter
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ProviderDetail extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long number;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Integer price;
@Column(nullable = false)
private Integer stock;
@ManyToOne
@JoinColumn(name = "provider_id")
@ToString.Exclude
private Provider provider;
}
- @OneToMany를 사용하는 입장에서는 어느 엔티티 클래스도 연관관계의 주인이 될수 없다.
9.4.2 다대일 양방향 매핑
- 공급업체 엔티티와 상품 엔티티의 일대다 연관관계 설청
@Entity
@Table(name = "product_detail")
@Getter
@Setter
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ProviderDetail extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "provider", fetch = FetchType.EAGER)
@ToString.Exclude
private List<Product> productList = new ArrayList<>();
}
9.4.3 일대다 단방향 매핑
- 상품분류 엔티티 클래스
@Entity
@Getter
@Setter
@NoArgsConstructor
@ToString
@EqualsAndHashCode
@Table(name = "category")
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long number;
@Column(unique = true)
private String code;
private String name;
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "category_id")
private List<Product> products = new ArrayList<>();
}
9.5 다대다 매핑
- 다대다(M:N) 연관관계는 실무에서 거의 사용되지 않는 구성이다.
- 다대다 연관관계를 상품과 생산업체의 예로 들자면 한 종류의 상품이 여러 생산업체를 통해 생산될 수 있고, 생산업체 한 곳이 여러 상품을 생산할 수도 있다.
- 다대다 연관관계에서는 각 엔티티에서 서로를 리스트로 가지는 구조가 만들어진다. 이런 경우에는 교차 엔티티라고 부르는 중간 테이블을 생성해서 다대다 관계를 일대다 또는 다대일 관계로 해소한다.
9.5.1 다대다 단방향 매핑
- 생산업체 엔티티
@Entity
@Table(name = "product")
@Getter
@Setter
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Provider extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String code;
private String name;
@manyToMany
@ToString.Exclude
private List<Product> products = new ArrayList<>();
public void addProduct(Product product) {
Product.add(Product);
}
}
9.5.2 다대다 양방향 매핑
- 상품 엔티티에서 생산업체 엔티티 연관관계 설정
@Entity
@Table(name = "product")
@Getter
@Setter
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Provider extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long number;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Integer price;
@Column(nullable = false)
private Integer stock;
@ManyToOne
@JoinColumn(name = "provider_id")
@ToString.Exclude
private Provider provider;
@manyToMany
@ToString.Exclude
private List<Product> products = new ArrayList<>();
public void addProduct(Product product) {
Product.add(Product);
}
}
9.6 영속성 전이
- 영속성 전이(cascade)란 특정 엔티티의 영속성 상태를 변경할 때 그 엔티티와 연관된 엔티티의 영속성에도 영향을 미쳐 영속성 상태를 변경하는 것을 의미한다.
종류 | 설명 |
ALL | 모든 영속 상태 변경에 대해 영속성 전이를 이용 |
PERSIST | 엔티티가 영속화할 때 연관된 엔티티도 함께 영속화 |
MERGE | 엔티티를 영속성 컨텍스트에 병합할 때 연관된 엔티티도 병합 |
REMOVE | 엔티티를 제거할 때 연관된 엔티티도 제거 |
REFRESH | 엔티티를 새로고침할 때 연관된 엔티티도 새로고침 |
DETACH | 엔티티를 영속성 컨텍스트에서 제외하면 연관된 엔티티도 제외 |
9.6.2 고아 객체
- JPA에서 고아(orphan)란 부모 엔티티와 연관관계가 끊어진 엔티티를 의미한다.
- JPA에는 이러한 고아 객체를 자동으로 제거하는 기능이 있다.
- 물론 자식 엔티티가 다른 엔티티와 연관관계를 가지고 있다면 이 기능은 사용하지 않는 것이 좋다.
@OneToMany(mappedBy = "provider", cascade = CascadeType.PERSIST, orphanRemoval = true)
- 'orphanRemoval = true' 속성은 고아 객체를 제거하는 기능이다.
9.7 정리
- JPA를 사용할 때 영속이라는 개념은 매우 중요하다.
- 코드를 통해 편리하게 DB에 접근하기 위해서는 중간에서 엔티티의 상태를 조율하는 영속성 콘텍스트가 어떻게 동작하는지 이해해야 한다.
'북터디 > 스프링 부트 핵심 가이드' 카테고리의 다른 글
스프링 부트 핵심 가이드 8장 [Spring Data JPA 활용] (0) | 2023.08.14 |
---|---|
스프링 부트 핵심 가이드 6장 [데이터베이스 연동] (0) | 2023.08.07 |
스프링 부트 핵심 가이드 5장 [API를 작성하는 다양한 방법] (0) | 2023.07.31 |
스프링 부트 핵심 가이드 4장 [스프링 부트 애플리케이션 개발하기] (0) | 2023.07.30 |
스프링 부트 핵심 가이드 3장 [개발 환경 구성] (0) | 2023.07.24 |