본문 바로가기
북터디/스프링 부트 핵심 가이드

스프링 부트 핵심 가이드 9장 [연관관계 매핑]

by vita12321 2023. 8. 20.
728x90
반응형

스프링 부트 핵심가이드 ( 부제 : 스프링 부트를 활용한 애플리케이션 개발 실무 ) - 장성우 지음

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 프로젝트 생성

 

https://yoonhs98.tistory.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%ED%95%B5%EC%8B%AC-%EA%B0%80%EC%9D%B4%EB%93%9C-4%EC%9E%A5-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0

 

스프링 부트 핵심 가이드 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에 접근하기 위해서는 중간에서 엔티티의 상태를 조율하는 영속성 콘텍스트가 어떻게 동작하는지 이해해야 한다.
728x90
반응형