study/Spring boot

JPA 연관관계 설정하기

올스왑 2021. 3. 25. 19:41

데이터끼리는 관계를 갖는다. 그래서 DB 테이블끼리 관계를 설정하기 위해 ForeignKey를 사용한다.

예를 들어 작성자 테이블과 게시글 테이블이 있다면 작성자는 여러 게시글을 작성할 수 있기 때문에 게시글은 작성자 FK를 갖고 있도록 설계해야 한다.

 

JPA는 이런 연관관계를 Entity에 어노테이션을 붙여 설정할 수 있다. 전자가 자신이고 후자가 상대다.

  • @OneToOne - 일대일
  • @OneToMany - 일대다
  • @ManyToOne - 다대일
  • @ManyToMany - 다대다

 

JPA를 통해 데이터들을 처리하려면 먼저 DB Table과 Java Entity를 만들어야 한다.

2021.03.24 - [study/Spring boot] - JPA와 Entity 기본

2021.03.25 - [study/Spring boot] - JPA와 Repository(간단히 CRUD)

 

위의 예와 같이 작성자와 게시글의 관계는 일대다 관계가 되고, 게시글 테이블은 작성자 FK를 갖는다.

 

그렇다면 게시글 엔티티에는 다음과 같은 필드가 있을 것이다.

@ManyToOne
private Writer writer;
// 엔티티의 연관관계를 설정할 때, 상대 객체 타입 그대로 기술한다.

작성자 엔티티는 게시글의 writer 필드와 매핑되는 연관관계를 설정한다. 일대다 관계이기 때문에 List 형태로 선언한다.

@OneToMany(mappedBy="writer")
private List<Board> board;

mappedBy는 연관관계에 대한 FK가 상대에게 있다는 것이고, 그렇다면 해당 필드에 대한 컬럼은 테이블에 없을 것이다.

mappedBy는 상대 엔티티에서 연결되는 필드 이름을 적는다.


연관관계를 설정할 때, FetchType을 정할 수 있다.

FetchType은 JPA가 하나의 Entity 를 조회할 때, 연관관계에 있는 객체들을 어떻게 가져올 것인지 정하는 설정값이다.

Eager와 Lazy 두가지로 나뉜다.

 

@ManyToOne은 FetchType.EAGER, @OneToMany는 Fetch.LAZY가 기본 설정이다.

 

예)

@OneToMany(fetch = FetchType.LAZY, mappedBy = "writer")
private List<Board> board;

 

LAZY : 지연 로딩. 호출하지 않는 이상 연관관계가 설정된 엔티티를 가져오지 않는다
EAGER : 즉시 로딩. 연관관계가 설정된 모든 엔티티에 대해 조인이 일어난다.

 

ManyToOne

  • FetchType=EAGER : 테이블의 모든 컬럼 정보를 가져오는 쿼리를 날렸을 때, join을 통해 연관관계에 있는 데이터도 가져온다.
  • FetchType=LAZY : 서비스에서 호출하는 경우에만 연관관계에 있는 데이텀를 가져온다. 이 경우 N+1 문제가 발생할 수 있다. 

OneToMany

  • FetchType=EAGER : ManyToOne과는 다르게, join문을 사용하지 않고, 연관관계에 있는 데이터를 select, where 문을 통해 가져온다.
  • FetchType=LAZY : 마찬가지로 서비스에서 호출하는 경우에만 연관관계에 있는 데이터를 가져온다.

* N+1 문제가 발생하는 경우 : ManyToOne, OneToOne 컬럼의 FetchType 을 LAZY로 했을 때 발생.

* N+1 문제 해결 방법

  • JPQL 의 Fetch Join을 사용.
  • ManyToOne, OneToOne 의 FetchType = EAGER 로 변경.
  • @EntityGraph 를 이용해, 한 쿼리에 대해서만 EAGER load 를 지정.

예)

게시글을 조회한다고 하자. 게시글은 작성자, 답글 테이블과 관계가 있다.
* 게시글을 조회할 때, 작성자 정보를 보이게 한다면 EAGER 전략으로 가져온다.
* 게시글을 조회할 때, 모든 답변을 보이게 한다면  EAGER 전략으로 가져온다.

* 하지만 댓글을 따로 보게 만든다면, LAZY 전략으로 가져온다.

 

출처 :  jaynewho.com/post/39


엔티티가 서로 상호 참조하는 경우, toSting() 메소드가 계속 호출되서 오버플로우가 발생한다.

따라서 해당 필드들에 대해서는 Lombok 어노테이션을 통해 toString()에서 해당 필드값을 exclude한다.
상호 참조하는 엔티티 중 하나만 제외처리를 해도 되지만, 공통성을 위해 둘 다 제외처리한다.

@ToString(exclude = {"orderGroup"})
public class User {
	....
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
    private OrderGroup orderGroup;
    .....
}

 

기존의 쿼리를 사용하면 join을 걸어 참조하는 다른 데이터를 가져온다. 하지만 JPA를 사용하면 객체 참조처럼 데이터를 가져온다. -> 쿼리에 대한 부담을 덜 수 있다.

 

다음과 같이 주문 상세 엔티티를 시작으로, 아이템 -> 파트너 -> 파트너사의 카테고리를 가져오면서, 쿼리가 아닌 연관관계를 이용한 게터 메소드로 데이터를 가져올 수 있다.

System.out.println("파트너사 카테고리 : " + orderDetail.getItem().getPartner().getCategory().getTitle());