개요
1:N 관계에서 1을 기준으로 연관된 데이터를 모두 가져오고자 하는 상황에서 @BatchSize를 통해 Batch Fetching을 시도했으나 LazyInitializationException이 발생했다.
대략적인 코드는 다음과 같다.
Order - 주문(1)
@Entity
class Order(
@Id
var orderKey: Long? = null,
@BatchSize(size = 100)
@OneToMany(mappedBy = "order")
var options: MutableList<Option> = mutableListOf(),
// 생략..
)
Option - 옵션(N)
@Entity
class Option(
@Id
var optionNo: Long? = null,
@JoinColumn(name = "ORDER_KEY")
@ManyToOne(fetch = FetchType.LAZY)
var orders: Order? = null,
// 생략...
}
OrderRepository
interface OrderRepository : JpaRepository<Order, Long> {
@Query("SELECT o FROM Order o WHERE o.orderKey IN :orderKeys")
fun findOrders(orderKeys: List<Long>): List<Order>
}
통합 테스트를 수행하니 다음과 같이 에러가 발생했는데.. (참고로 별도로 @Transactional는 붙이지 않은 상태였다)
해결
원인은 간단했다. Batch Fetching을 시도하는 시점이 연관관계를 맺고 있는 하위 엔티티를 로딩하는 시점이기 때문이다. 즉, Order를 SELECT하는 시점에 바로 IN 쿼리를 통해 연관된 Option을 가져오는 것이 아니고, 실제로 Option이 참조되는 시점에 IN 쿼리가 발생한다는 의미이다.
이는 곧 트랜잭션(정확히는 영속성 컨텍스트) 안에서만 Batch Fetching이 이루어질 수 있다는 의미인데, 나는 해당 테스트 코드에 별도로 트랜잭션을 걸어놓지 않았기 때문에 LazyInitializationException이 발생한 것이다.
@Transactional 어노테이션을 붙이면 정상적으로 테스트가 통과하는 것을 알 수 있다.
당연한 말이지만 서비스에서 해당 레포지토리 메서드를 호출할 때도 동일하게 트랜잭션이 걸려 있어야 N에 해당하는 객체의 값을 가져올 수 있다.