본문 바로가기

Server/Spring

[Spring] failed to lazily initialize a collection of role

문제

Seller 와 1:N 관계를 가지고 있는 SellerUsers 의 첫번째 요소를 가지고 오기 위해서 List 클래스의 first() 메소드를 호출하였을 때 예외가 발생하였습니다.

 

이와 관련하여 간단하게 코드로 구현하였습니다.

class Class1Service(
    private val sellerService: SellerService
    private val class2Service: Class2Service
) {
    fun call()(sellerId) {
        val seller = sellerService.findSeller(sellerId)

        class2Service.call(seller)
    }
}

Class1Service.call() 메소드에서 sellerId 를 통해 seller 를 조회하고, 조회한 seller 를 매개변수로 하는 class2Service.call() 메소드를 호출하였습니다.

 

class Class2Service() {
    fun call(seller: Seller) {
        val sellerUser = seller.sellerUsers.first()
        println("sellerUser")
    }
}

호출된 Class2Service.call() 메소드 내에서 sellerUser.sellerUsers.first() 를 호출하면 다음과 같은 예외가 발생합니다.

 

LazyInitializationException

Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: Seller.sellerUsers: could not initialize proxy - no Session

 

이는 seller.sellerUsers 를 사용할 때 영속성 컨텍스트가 종료되어 버려서 지연 로딩을 할 수 없어서 발생하는 오류입니다.

 

지연로딩(Lazy Loading)은 엔티티A를 조회할 때 연관되어 있는 다른 엔티티B 를 같이 조회하지 않고, 필요한 시점에 호출하는 것입니다. 즉, 쿼리가 두번 나갑니다. 엔티티 A 조회시 한번, 엔티티 B 조회 시 한번.

 

우리의 예시에 대입하면 seller 를 조회할 때 쿼리문을 날리고, sellerUser 변수에 프록시 객체를 넣어둡니다. 그리고 반환된 sellerUser 는 프록시 객체로 실제로 사용될 때까지 데이터 로딩을 지연시키고 추후에 호출할 때 sellerUser를 조회하여 프록시 객체에 값을 채웁니다. 이때, 프록시 객체가 실제로 엔티티로 바뀌는 것이 아니고, 프록시 객체를 통해서 엔티티에 접근합니다.

 

또 다른 개념인 즉시 로딩(Eager Loading)은 엔티티A 를 조회할 때 연관된 엔티티 B 를 함께 조회합니다. A join B 로 쿼리가 한번 나갑니다.

 

지연로딩을 사용하려면 @ManyToOne 의 fetch 속성을 FetchType.LAZY 로 설정해야 합니다. @OneToMany 는 default 값이 LAZY 이기 때문에 따로 설정할 필요가 없습니다. 현재 seller : sellerUser 는 @OneToMany 의 관계이기 때문에 지연로딩으로 설정되어 있습니다.

 

해결방법

해결방법은 간단했습니다. seller.sellerUsers 를 조회할 때 영속성 컨텍스트가 유지되도록 하는 것입니다. 이를 위해서 seller 를 조회하는 로직과 seller.sellerUsers 를 로직을 하나의 영속성 트랜잭션을 사용하도록 Class1Service.call() 메소드에 @Transactional 어노테이션을 사용하여 해결해주었습니다.