오늘의하루

[JPA] LazyInitializationException 발생! 본문

Spring/JPA

[JPA] LazyInitializationException 발생!

오늘의하루_master 2024. 2. 14. 13:56

예외 발생 이유

세션이 사라진 후 지연 로딩(LAZY) 상태에서 프록시 초기화가 불가능하기 때문에 발생하는 예외

지연 로딩 ( FetchType.LAZY )

지연 로딩을 하는 이유는 JPA에서 발생하는 문제 중 가장 유명한 N + 1 문제를 해결하기 위함이다.

지연 로딩은 한번에 연관된 데이터까지 한번에 불러오는 것이 아닌 연관된 데이터를 필요한 시점에 그때 그때 불러오는 것이다.

그래서 왜 지연 로딩을 쓰는가?

예를 들어 Member과 Team테이블이 있고 Member와 Team는 N:1 관계가 있다.

이때 즉시 로딩(EAGER)을 하게 된다면 Team을 조회 시 해당 Team을 가진 모든 Member를 하나씩 조회하게 된다.

분명 Team 1개를 조회했는데 Team에 속한 Member N개의 조회 쿼리가 나가는 문제를 N + 1 문제라고 하며 이런 문제를 지연 로딩(LAZY)로 쉽게 해결이 가능하기 때문이다.

해결 방안

  1. @Transactional Annotation
  2. Fetch Join
  3. @EntityGraph

@Transactional ✔

해당 예외의 근본적인 문제는 영속성 컨텍스트가 종료되고 트랜젝션이 끝났을 때 일어나는 예외이기 때문에 해당 문제를 근복적으로 해결할 수 있는 가장 좋은 해결 방안이다.

Fetch Join

Fetch Join은 JPQL에서 성능 최적화를 위해 제공하는 Join의 종류이다.

이는 연관된 Entity와 Collection을 한번에 조회할 수 있도록 해주는 기능이다.

Fetch Join을 사용하는 경우 Join 대상에는 별칭을 줄 수 없다.

// 해당 내용은 JPA 표준에서는 지원하지 않지만
// Hibernate 및 몇몇 구현체는 별칭을 지원하기 때문에 오류가 발생하지 않는다.
[❌] select t from Team t JOIN FETCH t.members m

[⭕] select t from Team t JOIN FETCH t.members

@EntityGraph

EntityGraph는 즉시 로딩(EAGER)와 다를 바 없지만 Distinct 설정이 가능하다는 장점이 있다.

결국 Left Outer Join으로 데이터를 읽어오게 된다.

// Member의 FetchSize는 LAZY이다.

public interface MemberRepository extends JpaRepository<Member, Long>{
    @EntityGraph(attributePaths = {"team"})
    List<Member> findEntityGraph();
}

select
    member0_.member_id ~,
    team1_.team_id ~,
    member0_.username ~,
    member0_.team_id ~,
    team1_.name
from 
    member member0_
left outer join
    team team1_ on member0_.team1_id = team1_.team_id
Comments