N+1(지연로딩, 즉시로딩) - JPA

Jiwon Park·2023년 5월 28일
0

*각 연관관계의 fetch 속성 default값

@ManyToOne : FetchType.EAGER
@OneToOne : FetchType.EAGER
@ManyToMany : FetchType.LAZY
@OneToMany : FetchType.LAZY


즉시로딩(EAGER)은 연관된 엔티티를 모두 조회한다.
-> 원하지 않을 때도 불필요한 조인 발생 -> 성능 저하

더 큰 문제는 find()로 조회할 경우 JPA가 내부적으로 join(default - OUTER JOIN)을 사용해서 최적화를 해주지만, JPQL이 사용되는 경우 sql 그대로 쿼리가 나가 join이 안 걸린다
-> 연관 엔티티의 쿼리가 각각 따로 실행된다
-> n+1 발생 -> 데이터가 많아지면 엄청난 성능 저하


지연 로딩(LAZY) 을 사용하면 연관 엔티티는 프록시로 가져왔다가 실제 사용 할 때 쿼리가 나간다.
하지만 연관 엔티티를 추가 조회 할때 쿼리가 추가 실행되기 때문에 n+1 발생


N+1

N+1문제란 연관관계에 있는 테이블을 조회할 때 1개의 쿼리를 날렸는데 최악의 경우(영속성 컨텍스트에서 한개도 못가져올 경우) 결과 수 만큼 엔티티마다 n번의 쿼리를 추가 실행한다는 의미이다.

지연 로딩을 걸고 jpql에 일반 조인을 걸어도 지연 로딩이 걸려 있기 때문에 여전히 프록시로 가져온다.
-> n+1 발생

N+1 해결책

fetch join을 사용하자.
엔티티에 지연 로딩이 걸려 있어도 그보다 우선하는 즉시 로딩을 해주며 연관된 엔티티 모두 조인 시점에 한번에 불러온다.

-> 지연 로딩을 베이스로 최적화가 필요한 곳은 패치 조인을 적용하자


WARN 22836 --- [nio-8080-exec-2] o.h.h.internal.ast.QueryTranslatorImpl : HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!

*일대다처럼 컬렉션 조회에서 페이징을 해야 할 경우 패치 조인으로 하면 디비에선 distinct가 정상적으로 잘 안되서 메모리로 가져와 페이징을 하게 되므로 Out of Memory를 방지하려면 BatchSize를 사용하자.




*즉시로딩 참고

가져오는 쿼리의 결과 차이가 커질수록 내부 조인이 성능상 유리하다.

@JoinColumn(nullable = true) : NULL 허용 O, 외부 조인 사용 - default값

@JoinColumn(nullable = false) : NULL 허용 X, 내부 조인 사용
또는 @ManyToOne.optional = false

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "teamId", nullable = false)
private Team team;

또는

@ManyToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "teamId")
private Team team;
profile
안녕하세요

0개의 댓글