제목, 태그, 카테고리로 검색

모든 글
약 5분 분량 프로젝트/에듀밋

Lazy 로딩에서 발생한 No Session 오류

목차

정상 상태

Board(게시글)와 BoardImage(첨부파일)는 @OneToMany 관계로 매핑되어 있어요. @OneToMany의 기본 fetch 전략은 FetchType.LAZY로, 게시글을 조회하면 Board 엔티티만 먼저 로딩되고, BoardImage는 실제로 접근하는 시점에 별도 SELECT가 실행되는 구조예요.

즉, Board 조회 → BoardImage 접근 순서로 총 2번의 SELECT가 실행되어야 정상이에요.


문제 상황

단위 테스트에서 Board를 조회한 뒤 BoardImage에 접근하려 하자, LazyInitializationException: no session 오류가 발생했어요.

실행 결과를 보면 Board까지의 출력은 정상적으로 끝났지만, 그 직후 BoardImage를 SELECT하려는 시점에 DB 세션이 이미 닫혀 있었어요.


원인 분석

Lazy 로딩은 영속성 컨텍스트(Persistence Context)가 살아 있는 동안에만 동작해요. 영속성 컨텍스트는 트랜잭션 범위와 생명주기가 같기 때문에, 트랜잭션이 없는 테스트 메서드에서는 첫 번째 SELECT(Board 조회)가 끝나는 즉시 영속성 컨텍스트가 닫혀요.

이 상태에서 BoardImage에 접근하면, Hibernate가 프록시 객체를 초기화하려고 하지만 이미 세션이 없으므로 no session 오류가 발생해요.

핵심은 Lazy 로딩 = 프록시 초기화 = 영속성 컨텍스트 필요라는 점이에요. 테스트 코드에 트랜잭션이 없으면 이 전제가 깨지죠.


해결 과정

1차 시도: @Transactional 추가

가장 단순한 해결책은 테스트 메서드에 @Transactional을 추가하는 거예요. 이 어노테이션이 적용되면 메서드 전체가 하나의 트랜잭션으로 감싸지므로, 메서드 내에서 추가 쿼리를 여러 번 실행할 수 있어요.

하지만 이 방법은 테스트 환경에서만 유효한 우회책이에요. 실제 서비스 코드에서는 트랜잭션 범위 밖에서 Lazy 엔티티에 접근하는 상황이 동일하게 발생할 수 있거든요.

2차 시도: @EntityGraph 적용

근본적인 해결을 위해 @EntityGraph를 적용했어요. @EntityGraph는 JPA가 제공하는 어노테이션으로, 지정한 연관 엔티티를 조회 시점에 함께 로딩(Eager)하도록 선언할 수 있어요.

@EntityGraphattributePathsimageSet을 명시하여, Board 조회 시 BoardImage를 한 번에 가져오도록 설정했어요.

단위 테스트를 다시 실행한 결과:

실행 결과의 쿼리를 보면, Board 테이블과 BoardImage 테이블이 LEFT JOIN으로 한 번에 조회됐어요. 별도의 추가 SELECT 없이 게시글과 첨부파일을 동시에 처리할 수 있게 된 거예요.


정리

구분@Transactional@EntityGraph
해결 방식트랜잭션 범위 확장조인으로 한 번에 조회
쿼리 수2회 (Board + BoardImage)1회 (JOIN)
적용 범위테스트 한정 우회서비스 코드에서도 적용 가능
근본 해결아님맞음

@OneToMany 구조에서 Lazy 로딩을 유지하면서도, 필요한 시점에 @EntityGraph로 Eager 전환을 선언할 수 있다는 점이 핵심이에요. 하위 엔티티의 로딩 전략을 상황에 따라 유연하게 제어할 수 있죠.


Reference

Normal Behavior

Board (post) and BoardImage (attachment) are mapped with a @OneToMany relationship. The default fetch strategy for @OneToMany is FetchType.LAZY, meaning when a post is retrieved, only the Board entity is loaded first, and BoardImage triggers a separate SELECT when actually accessed.

In other words, Board retrieval → BoardImage access should result in 2 SELECT queries total.


The Problem

In a unit test, after retrieving a Board and trying to access its BoardImage, a LazyInitializationException: no session error occurred.

The execution output showed that Board was printed successfully, but at the point of trying to SELECT BoardImage, the DB session had already closed.


Root Cause Analysis

Lazy loading only works while the Persistence Context is alive. Since the Persistence Context shares its lifecycle with the transaction, in a test method without a transaction, the Persistence Context closes immediately after the first SELECT (Board retrieval).

When BoardImage is accessed in this state, Hibernate tries to initialize the proxy object but fails because the session no longer exists, resulting in the no session error.

The key insight is: Lazy loading = Proxy initialization = Persistence Context required. Without a transaction in test code, this premise breaks.


Resolution

First Attempt: Adding @Transactional

The simplest fix is adding @Transactional to the test method. This wraps the entire method in a single transaction, allowing multiple queries within the method.

However, this approach is only a workaround valid in the test environment. In actual service code, the same situation of accessing Lazy entities outside the transaction scope can occur.

Second Attempt: Applying @EntityGraph

For a fundamental solution, @EntityGraph was applied. @EntityGraph is a JPA annotation that declares specified related entities to be loaded eagerly at query time.

By specifying imageSet in @EntityGraph’s attributePaths, Board retrieval now fetches BoardImage in a single query.

Re-running the unit test:

The query log shows that Board and BoardImage tables were retrieved in a single LEFT JOIN. Posts and attachments can now be processed simultaneously without additional SELECTs.


Summary

Aspect@Transactional@EntityGraph
ApproachExtend transaction scopeJoin in single query
Query Count2 (Board + BoardImage)1 (JOIN)
ApplicabilityTest-only workaroundApplicable in service code too
Fundamental FixNoYes

The key takeaway is that while maintaining Lazy loading in a @OneToMany structure, you can declare Eager fetching with @EntityGraph when needed. This allows flexible control over child entity loading strategies based on the situation.


Reference

Author
작성자 @범수

오늘의 노력이 내일의 전문성을 만든다고 믿습니다.

댓글