개발을 하다 보면 JPA와 Hibernate를 사용하는 환경에서 LazyInitializationException을 마주치게 될 때가 있습니다. 특히 연관 데이터를 지연 로딩(Lazy Loading)으로 설정했을 때 트랜잭션 종료 이후 데이터를 로드하려 하면 이 문제가 발생합니다. 이번 글에서는 LazyInitializationException이 무엇인지, 왜 발생하는지, 그리고 이를 해결하기 위해 필요한 트랜잭션, 영속성 컨텍스트, 준영속성에 대해 설명합니다.

 

 

 

1. LazyInitializationException이란?

LazyInitializationException은 JPA에서 지연 로딩(Lazy Loading)으로 설정된 연관 데이터를 트랜잭션 종료 이후 로드하려고 할 때 발생하는 예외입니다. Lazy Loading은 실제로 데이터가 필요할 때만 쿼리를 실행하여 데이터를 가져오는 방식이지만, 트랜잭션이 종료된 후에는 영속성 컨텍스트가 닫혀 데이터베이스에 접근할 수 없으므로 예외가 발생합니다.

 

 

2. 예제 코드

Circle 엔티티와 CircleSchedule 엔티티를 사용한 예제를 통해 설명합니다.

@Entity
class Circle(
    @Id
    @GeneratedValue
    val id: UUID = UUID.randomUUID(),

    val title: String,

    @OneToMany(mappedBy = "circle", fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
    val circleSchedules: MutableList<CircleSchedule> = mutableListOf()
)


CircleSchedule 엔티티

@Entity
class CircleSchedule(
    @Id
    @GeneratedValue
    val id: UUID = UUID.randomUUID(),

    @ManyToOne(fetch = FetchType.LAZY)
    val circle: Circle,

    val dayOfWeek: String,
    val startTime: String,
    val endTime: String
)

 

 

3. 문제 상황: LazyInitializationException 발생

아래 코드에서 circle.circleSchedules는 Lazy Loading으로 설정되어 있습니다. 트랜잭션이 종료된 후 circleSchedules에 접근하려 하면 LazyInitializationException이 발생합니다.

 

문제 코드

fun getCircleSchedules(circleId: UUID): List<CircleSchedule> {
    val circle = circleRepository.findById(circleId)
        .orElseThrow { NotExistException("Circle not found") }
    return circle.circleSchedules // LazyInitializationException 발생 가능
}

 

발생 이유

  1. circle.circleSchedules는 FetchType.LAZY로 설정되어 있어, 기본적으로 데이터베이스 쿼리를 실행하지 않습니다.
  2. 트랜잭션이 종료되면 영속성 컨텍스트도 종료되므로, 지연 로딩된 데이터를 가져올 수 없습니다.

 

4. LazyInitializationException 해결 방법

1) @Transactional 사용

트랜잭션 내에서 지연 로딩 데이터에 접근하도록 @Transactional을 적용합니다.

@Transactional(readOnly = true)
fun getCircleSchedules(circleId: UUID): List<CircleSchedule> {
    val circle = circleRepository.findById(circleId)
        .orElseThrow { NotExistException("Circle not found") }
    return circle.circleSchedules // Lazy Loading 가능
}

 

2) Open-Session-In-View 설정

Spring Boot에서 spring.jpa.open-in-view 설정을 활성화하면 트랜잭션이 종료된 이후에도 Lazy Loading이 가능합니다. 하지만 이 방법은 권장되지 않습니다.

 

application.yml 설정:

spring:
  jpa:
    open-in-view: true

 

 

5. 영속성 컨텍스트와 트랜잭션

영속성 컨텍스트(Persistence Context)

영속성 컨텍스트는 JPA 엔티티를 관리하며 데이터베이스와의 상호작용을 처리하는 캐시 역할을 합니다.

  • 트랜잭션이 시작되면 영속성 컨텍스트가 활성화됩니다.
  • 트랜잭션이 종료되면 영속성 컨텍스트도 종료됩니다.
  • Lazy Loading은 영속성 컨텍스트 내에서만 작동합니다.

트랜잭션 종료 이후의 Lazy Loading

트랜잭션이 종료되면 영속성 컨텍스트가 종료되고, Lazy Loading 데이터는 로드할 수 없습니다. 이를 방지하려면 트랜잭션 내부에서 데이터를 로드해야 합니다.

 

 

6. 결론

  • LazyInitializationException은 트랜잭션 종료 이후 Lazy Loading 데이터를 로드하려 할 때 발생합니다.
  • @Transactional 등을 활용하여 LazyInitializationException을 방지할 수 있습니다.
  • 영속성 컨텍스트와 트랜잭션의 생명주기를 이해하면 Lazy Loading 문제를 더 잘 해결할 수 있습니다.

'JPA' 카테고리의 다른 글

Kotlin, JPA 환경에서 Entity 설계에 대한 고민  (0) 2024.09.08
자바 ORM 표준 JPA 프로그래밍 기본편 정리 1탄  (0) 2022.08.07
@Enumerated  (0) 2022.07.25

+ Recent posts