JPA 연관관계 설정하기
데이터끼리는 관계를 갖는다. 그래서 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());