[JPA] FetchType 즉시로딩, 지연로딩 정리
오늘은 JPA를 이용해 개발을 진행하다보면 자주 접하는 Fetch Type에 대해 알아볼 것이다.
Fetch Type 속성은 언제 어떻게 동작하며, 전에 작성한 N+1 문제와 어떤 관련이 있는지, 그리고 어떻게 해결할 수 있는지에 대해 정리한다.
Fetch Type이란?
JPA가 하나의 Entity를 조회할 때, 연관관계에 있는 객체를 어떻게 가져올 것이냐를 나타내는 설정값이다.
크게 Eager와 Lazy 두가지 전략이 있다. Fetch Type Issue 상황은 하나의 Entity를 로드할 때 두가지 전략 중 고민하는 상황을 말한다.
- 연관관계에 있는 Entity들 모두 가져온다 - Eager
- 연관관계에 있는 Entity 가져오지 않고, Getter로 접근할 때 가져온다 - Lazy
각 연관관계의 default 속성
- @ManyToOne : EAGER
- @OneToOne : EAGER
- @ManyToMany : LAZY
- @OneToMany : LAZY
JPA 기본 페치 전략
- @ManyToOne, @OneToOne : 즉시 로딩 (optional = false : 내부 조인, optional = true : 외부 조인)
- @OneToMany, @ManyToMany : 지연 로딩 (optional = false : 외부 조인, optional = true : 외부 조인)
즉시로딩과 지연로딩
1) 즉시로딩 EAGER
- 특정 엔티티를 조회할 때 연관된 모든 엔티티를 같이 로딩
- 항상 외부 조인 (OUTER JOIN)을 사용 (외부 조인보다 내부 조인(INNER JOIN)이 성능 최적화에 더 유리)
- 실무에서 엔티티 간 관계가 복잡해질 수록 조인으로 인한 성능 저하를 피할 수 없고 JPQL에서 N+1문제를 일으킴
- 지연로딩을 기본으로 사용하고 상황에 맞게 사용하길 권장
2) 지연로딩 LAZY
- 연관된 엔티티를 프록시로 조회
- 프록시를 실제 사용시 초기화하면서 데이터베이스를 조회
- 지연로딩 적용 상태에서 연관관계 상태의 종(Member와 Order 중 Member)에 접근하려고 하면 proxy [~] - no Session과 같은 에러 메세지
- 이미 DB와 연결된 Connection에 커밋을 날리고 트랜잭션이 닫힌 상태, 연결된 Connection이 없음
- @Transactional 어노테이션을 통해 해결 (해당 메소드를 하나의 트랜잭션으로 처리) -> 필요할 때마다 다시 데이터베이스와의 연결이 생성되어 정상적으로 실행
- 조회 대상이 영속성 컨텍스트에 이미 있으면 프록시 객체를 사용할 이유가 없음. 따라서 영속성 컨텍스트에 이미 로딩되어 있으면 프록시 객체가 아닌 실제 객체(엔티티)를 사용
즉시로딩에서 외부 조인을 사용하는 이유?
- 다대일 관계인 회원 테이블과 팀 테이블을 조인할 때 회원 테이블의 외래 키에 not null 제약조건을 걸어두면 모든 회원은 팀에 소속되므로 항상 내부 조인을 사용해도 된다.
- 반대로 팀 테이블에서 회원 테이블로 일대다 관계를 조인할 때 회원이 한 명도 없는 팀을 내부 조인하면 팀까지 조회되지 않는 문제가 발생한다. 데이터베이스 제약조건으로 이런 상황을 막을 수 없다.
- 따라서 JPA는 일대다 관계를 즉시 로딩할 때 항상 외부 조인을 사용한다.
글로벌 페치 전략에 즉시로딩 사용시 단점
- 사용하지 않는 엔티티를 로딩한다
- N+1 문제 발생
- JPA는 JPQL을 분석해서 SQL을 생성할 때 글로벌 페치 전략을 참고하지 않고 오직 JPQL 자체만 사용
- 따라서 즉시 로딩이든 지연 로딩이든 구분하지 않고 JPQL 쿼리 자체에 충실하게 SQL을 만듦
- 만약 order : member = N : 1인 ManyToOne 관계에서 조회한 order 엔티티가 10개이면 member를 조회하는 SQL도 10번 실행 -> 처음 조회한 데이터 수만큼 다시 SQL을 사용해서 조회하는 것을 N+1 문제
- N+1이 발생하면 SQL이 상당히 많이 호출되므로 조회 성능에 치명적 -> JPQL Fetch join으로 해결
Fetch Type의 동작 시점
JPA Entity Manager 에 의해 관리되는 Persistence Context 에 Entity가 Managed 상태로 올라올 때의 동작이다.
queryDSL 과 같은 쿼리 빌더를 이용해 아무리 Join 문을 짜도 Fetch Join 을 하지 않는 이상 메인 도메인의 엔티티만 Persistence Context 에 올라온다.
연관관계에 대한 Fetch 도 메인 도메인만 일어난다.
# Referencse
JPA 의 Fetch Type 과 친해지기 | Jayne.who();
< Back JPA 의 Fetch Type 과 친해지기 web | 06 August 2019 Tags | java spring jpa JPA 를 이용한 개발을 하다보면 자주 접하는 프로그래밍적인 이슈가 있습니다. 바로 Fetch Type (Fetch 전략) 입니다. Fetch Type 속성은
jaynewho.com
[JPA] 즉시 로딩, 지연 로딩 | FetchType.EAGER, FetchType.LAZY
즉시 로딩 FetchType.EAGER 연관된 엔티티를 즉시 조회한다. 하이버네이트는 가능하면 SQL 조인을 사용해서 한 번에 조회한다. 즉시 로딩을 사용하려면 @ManyToOne의 fecth 속성을 FetchType.EAGER로 지정한다.
dar0m.tistory.com