문제
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 어노테이션을 사용하여 해결해주었습니다.
'Server > Spring' 카테고리의 다른 글
[Spring/JPA] EntityListener 로 효율적인 이벤트 처리를 구현해보도록 하겠습니다. (1) | 2024.09.06 |
---|---|
[Spring] 스프링 4.2 이후 version 에서 Application Event 도입을 해보았습니다. (0) | 2024.09.02 |
[Spring/JPA] 엔티티 생명주기(Entity Lifecycle) 에 대해 알아보겠습니다. (0) | 2023.10.20 |
[Spring/JPA] 영속성 컨텍스트(Persistence Context)에 대해 알아보겠습니다. (0) | 2023.10.17 |
[Spring] 스프링 컨테이너와 빈 객체의 생명 주기를 알아보겠습니다. (1) | 2023.10.06 |