본문 바로가기

공부/백엔드

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

반응형

1) 두 엔티티 간 생성할 수 있는 연관관계 

  1. 일대일 (One To One)
  2. 다대일 (Many To One)
  3. 일대다 (One To Many)
  4. 다대다 (Many To Many)

- DB에서는 컬럼에 외래키 속성을 주면 다른 테이블의 기본키를 얻을 수 있고, Join으로 참조할 수 있다. 

- JPA에서는 엔티티에서 참조 방향에 맞게 관계를 설정한다.

 

2) 관계 매핑 방향

  • 단방향: 한쪽이 다른 한쪽의 엔티티만 참조하는 관계
  • 양방향: 양쪽이 서로 참조할 수 있는 관계

-> (일대일, 다대일, 일대다, 다대다) - (단방향, 양방향) 각 조합이 모두 나올 수 있다.

-> 객체 지향 관점에서이며, 실제 DB에선 따로 방향이 없다. (외래키가 있는 쪽, 없는 쪽, 또는 다대다)

 


1.1. 일대일 (1:1) 단방향

: 두 엔티티가 서로 하나씩만 매핑될 수 있고 한 쪽에서만 참조할 수 있다.

@OneToOne
@JoinColumn(name="product_id")
private Product product;

 

- owner 엔티티(외래키를 가질 엔티티)에서 다른 엔티티 객체를 필드로 갖고, OneToOne 어노테이션으로 매핑한다.

 

OneToOne

  • optional: true (nullable), false (notNull)
  • fetch: EAGER (즉시 로딩)
  • mappedBy: (양방향에서) 상대 엔티티에도 읽기 전용으로 매핑 정보를 줄 수 있으며, owner 엔티티에서의 필드 이름(나의 id를 외래키로 갖고 있는 필드의 이름)을 값으로 넣는다. -> mappedBy가 설정된 엔티티는 주인이 아니므로 이 값이 바뀌어도 DB에 영향을 주지 않는다.

 

JoinColumn

  • name: 외래키 컬럼 이름 설정 - 선언하지 않아도 자동으로 이름이 생성되지만 매핑하는 중간 테이블이 생긴다.
  • referencedColumnName: 외래키가 참조할 상대 테이블의 컬럼명 - 기본적으로 기본키(id) 값이 들어간다.
  • foreignKey: 외래키 제약조건 설정 (unique, nullable, insertable, updatable)

- repository에서 참조하는 엔티티를 조회해오면 left outer join을 이용해 양쪽 객체를 함께 조회한다. (즉시 로딩)

(left outer join : 매핑된 외래키가 없어도 현재 엔티티의 값은 모두 출력)

- 외래키를 가지고 있는 테이블이 그 외래키 컬럼의 이름을 표시하기 위해 사용한다.

 

 

1.2. 일대일 (1:1) 양방향

: 일대일 단방향에서 매핑하지 않았던 다른 엔티티도 일대일 매핑한다.

  • mappedBy 설정을 하지 않으면 양쪽에 외래키가 생겨 동일한 두 테이블 간 join이 두 번 일어나 비효율적이게 된다.
  • ToString.Exclude로 순환 참조를 방지해야 한다.

-> 필요하지 않으면 일대일 단방향 매핑을 사용한다.

 

 

2.1. 다대일 (n:1) 단방향

: 엔티티의 한 컬럼이 다른 엔티티의 여러 컬럼에 매핑될 수 있는 경우에서 n에 해당하는 엔티티에 1의 외래키를 지정하는 것.

@ManyToOne
public Provider provider;

 

- 여러 컬럼이 올 수 있는 엔티티에서 매핑할 다른 엔티티 객체에 ManyToOne 어노테이션을 추가한다.

 

 

2.2. 다대일 (n:1) 양방향 - 일대다 (1:n)

: 다대일에서 양방향으로 연결하기 위해 1에 해당하는 엔티티에 일대다 속성을 추가한다.

@OneToMany(mappedBy="provider")
public List<Product> products = new ArrayList<>();

 

OneToMany

  • 여러 외래키가 매핑될 수 있으므로 컬렉션(List 등)으로 받는다.
  • JoinColumn을 사용하여 상대 엔티티에 추가할 외래키를 설정.
  • 디폴트 fetch 전략은 Lazy(지연로딩)으로 연관된 데이터를 가져오지 않고 현재 테이블 데이터만 가져온다.

 

3. 일대다 (1:n) 단방향

: 1에 해당하는 엔티티에서 매핑 정보를 가지는 경우.

  • 설정한 엔티티가 아닌 상대 엔티티에 외래키가 생성된다. => Many(List) 필드를 가지는 엔티티는 주인이 될 수 없다.
  • 외래키 설정을 위해 해당 엔티티 리스트에 추가하여 저장해야 하며 추가한 테이블에 대한 update 쿼리가 발생한다.

-> 다대일 연관관계를 쓰는 것이 좋다.

 

 

4. 다대다 (n:n)

 

: 한 테이블의 여러 데이터가 다른 테이블의 여러 데이터와 매핑될 수 있는 경우이며 각 엔티티에서 서로를 리스트로 가진다.

  • 중간 테이블(교차 엔티티)을 생성하여 일대다 구조로 만들어 사용한다.
  • 중간 엔티티를 만들어 JPA에서 관리할 수 있게 하는 것이 좋다.
  • 실무에서는 잘 사용되지 않는 구조

 

4.1. 다대다 (n:n) 단방향

// Producer Entity
@ManyToMany
@JoinTable(name="middle_table")
private List<Product> products = new ArrayList<>();

// 사용
Producer producer = new Producer();
producer.setName("name");

List<Product> list = new ArrayList<>();
list.add(new Product()); // 저장될 Product는 DB에 저장되어 있어야 함.
producer.setProducts(list);

producerRepository.save(producer);

 

: 한쪽에 ManyToMany로 다른쪽 엔티티 List를 필드로 갖는다. 중간 테이블이 생기므로 실제 양쪽 엔티티에 외래키 컬럼이 생기지는 않는다.

 

4.2. 다대다 (n:n) 양방향

: 단방향에서 다른 엔티티에도 ManyToMany 어노테이션으로 필드를 추가한다. 실제 DB의 테이블 구조는 단방향과 같다.

 


 

영속성 전이

: 엔티티의 영속성 상태를 변경할 때 그 엔티티와 연관된 엔티티의 영속성도 변경하는 것

 

cascade

: 연관관계매핑 어노테이션에 정의된 영속성 전이 설정에 사용하는 요소로 주어진 상태 변경이 일어나면 매핑돼있는 엔티티에도 동일한 동작이 일어나도록 한다.

  • ALL : 모든 변경에 대해 영속성 전이 (아래 속성 모두 포함)
  • PERSIST : 엔티티가 영속화할 때 연관된 엔티티도 함께 영속화 (비영속 -> 영속)
  • MERGE : 엔티티를 영속성 컨텍스트에 병합할 때 연관된 엔티티도 병합 (준영속 -> 영속)
  • REMOVE : 엔티티를 제거할 때 연관된 엔티티도 제거 (삭제)
  • REFRESH : 엔티티를 새로고침할 때 연관된 엔티티도 새로고침 (영속 엔티티 갱신)
  • DETACH : 엔티티를 영속성 컨텍스트에서 제외하면 연관된 에티티도 제외 (영속 -> 준영속)

- 엔티티 생명주기: 비영속, 영속, 준영속, 삭제

- 리스트로 여러개 사용 가능

 

persist

// Provider 엔티티에서 Product 엔티티를 영속성 전이
@OneToMany(mappedBy="provider", cascade = Cascade.PERSIST)
@ToString.Exclude
private List<Product> products = new ArrayList<>();

// 사용
Provider provider = new Provider(..);

Product product1 = new Product(..);
Product product2 = new Product(..);

provider.getProducts().addAll(Lists.newArrayList(product1, product2));

providerRepository.save(provider); // Product 테이블에도 product1, product2가 저장됨

 

=> 엔티티를 저장하면 cascade로 매핑돼있는 엔티티를 따로 저장하지 않아도 저장할 수 있게 된다.

 

 

고아 객체

: 부모 엔티티와 연관관계가 끊어진 엔티티.

 

  • 연관관계 어노테이션에서 orphanRemoval = true로 주면 연관관계가 끊어진 엔티티를 자동 삭제해준다.
@OneToMany(orphanRemoval=true)
private List<Product> products = new ArrayList<>();

 

-> 위 엔티티의 Product List에서 데이터를 삭제하면 Product 테이블의 데이터도 삭제된다.

반응형