본문 바로가기

JPA

[JPA] LazyInitializationException

개인 프로젝트로 e-commerce 서비스를 개발하던 중 LazyInitializationException 에러가 발생하였다.

LazyInitializationException 발생 이유

서비스단에서 @Transactional을 선언한 method가 종료되면 트랜잭션을 커밋한 후 트랜잭션과 영속성 컨텍스트가 종료된다. 영속성 컨텍스트가 종료되었으므로 조회한 엔티티가 준영속 상태가 되었고 그렇기 때문에 더이상 영속성 컨텍스트의 특징 중 하나인 지연로딩을 사용할 수 없다. 이때 컨트롤러단에서 FetchType.LAZY로 매핑된 테이블을 조회해오면 LazyInitializationException이 발생한다.

LazyInitializationException 해결 과정

  • 영속성 컨텍스트가 닫히기 전 서비스단에서 모든 로직을 구현하고 메소드마다 @Transactional 어노테이션을 걸어 주었는데도 LazyInitializationException 에러가 발생했다.
  • 서비스단에서 컨트롤러단으로 값을 전달할 때 리스트의 제네릭타입을 DTO가 아닌 CartProduct 엔티티로 전달한 것이 문제였고 CartProduct 엔티티로 조회한 뒤 stream().map().collect(toList())로 타입 변경한 후 전달하여 문제를 해결했다.
  • 서비스단의 코드를 간소화 하고자 조회할 때 DTO로 조회를 하도록 구현하였고 DTO 매핑이 쉬운 Querrydsl을 적용했다.

리스트 타입이 CartProduct

 

@Transactional(readOnly = true)
public List<CartProduct> getAllCartProduct(Long userId) {
    Cart cart = cartRepository.findByUserId(userId)
            .orElseThrow(() -> new ErrorCustomException(ErrorCode.NO_AUTHENTICATION_ERROR));
    List<CartProduct> cartProductList = cartProductRepository.findAllByCartId(cart.getId());
    return cartProductList;
}

 

리스트 타입을 CartProductResponDto로 수정

 

@Transactional(readOnly = true)
public List<CartProductResponseDto> getAllCartProduct(Long userId) {
    Cart cart = cartRepository.findByUserId(userId)
            .orElseThrow(() -> new ErrorCustomException(ErrorCode.NO_AUTHENTICATION_ERROR));
    List<CartProduct> cartProductList = cartProductRepository.findAllByCartId(cart.getId());
    List<CartProductResponseDto> responseDto = cartProductList
            .stream()
            .map(o -> new CartProductResponseDto(o))
            .collect(toList());
    return responseDto;
}

 

Querrydsl을 적용하여 CartProductResponseDto로 장바구니 조회

 

@Transactional(readOnly = true)
public List<CartProductResponseDto> getAllCartProduct(Long userId) {
    Cart cart = cartRepository.findByUserId(userId)
            .orElseThrow(() -> new ErrorCustomException(ErrorCode.NO_AUTHENTICATION_ERROR));
    List<CartProductResponseDto> responseDto = cartProductRepository.findCartProductByCartId(cart.getId());
    return responseDto;
}

 

CartProductResponseDto로 매핑된 조회 sql문 작성

 

@RequiredArgsConstructor
public class CartProductRepositoryImpl implements CartProductRepositoryCustom {

    private final JPAQueryFactory queryFactory;

    @Override
    public List<CartProductResponseDto> findCartProductByCartId(Long cartId, Pageable pageable) {
        return queryFactory
                .select(Projections.constructor(CartProductResponseDto.class,
                    cartProduct.id,
                    product.id,
                    product.productname,
                    product.productinfo,
                    product.productprice,
                    cartProduct.productcount
                ))
                .from(cartProduct)
                .join(cartProduct.product, product)
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .where(cartProduct.cart.id.eq(cartId))
                .groupBy(cartProduct.id)
                .orderBy(cartProduct.modifiedAt.desc())
                .fetch();
    }
}