본문 바로가기

Development/Diary

[개발 일기] OSIV가 false일 때 LazyInitializationExceptions

이거 때매 거의 이틀을 삽질했다.... 이해가 안되어서....

OSIV=false + getReferenceId - @Transactional = LazyInitializationExceptions

문제의 코드

@Override
    public Product selectProduct(Long number) {
        Product selectedProduct = productRepository.getReferenceById(number);

        return selectedProduct;
    }

다음과 같이 getReferenceById()를 사용한 객체를 리턴하고,

@Override
    public ProductResponseDto getProduct(Long number) {
        Product product = productDAO.selectProduct(number);

        ProductResponseDto productResponseDto = ProductResponseDto.builder()
                .number(product.getNumber())
                .name(product.getName())
                .price(product.getPrice())
                .stock(product.getStock())
                .build();

        return productResponseDto;
    }

그 객체를 외부 메서드에서 사용한다면, 이런 에러가 발생한다.

org.hibernate.LazyInitializationException: could not initialize proxy [org.spring.study.data.entity.Product#1] - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165) ~[hibernate-core-6.4.1.Final.jar:6.4.1.Final]
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:314) ~[hibernate-core-6.4.1.Final.jar:6.4.1.Final]
    at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:44) ~[hibernate-core-6.4.1.Final.jar:6.4.1.Final]
    at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:102) ~[hibernate-core-6.4.1.Final.jar:6.4.1.Final]
    at org.spring.study.data.entity.Product$HibernateProxy$0VRfTOUF.getName(Unknown Source) ~[main/:na]
    at org.spring.study.data.service.impl.ProductServiceImpl.getProduct(ProductServiceImpl.java:28) ~[main/:na]
    at org.spring.study.data.controller.ProductController.getProduct(ProductController.java:23) ~[main/:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~
    ...

메서드에 @Transactional를 적용하면 잘 동작한다.

@Override
@Transactional
    public ProductResponseDto getProduct(Long number) {
        Product product = productDAO.selectProduct(number);

        ProductResponseDto productResponseDto = ProductResponseDto.builder()
                .number(product.getNumber())
                .name(product.getName())
                .price(product.getPrice())
                .stock(product.getStock())
                .build();

        return productResponseDto;
    }

발생 원인?

이게 뭐가 문제냐면..

  1. getReferenceId는 지연 로딩(Lazy Loading)을 사용하므로 빈 프록시 객체를 리턴한다.
  2. OSIV가 활성화 되어있다면 외부 메서드에서 프록시 객체를 사용해도 문제가 되지 않는다. 왜냐면 Session과 Transaction이 쭉 유지되어 있기 때문에
  3. 그런데 OSIV가 비활성화 되어 있을 경우, getReferenceId 메서드가 끝나면 자동으로 Session과 Transaction이 닫힌다. 따라서 getReferenceId 메서드 외부에서 프록시 객체를 사용하면 LazyInitialzationException이 발생한다.

근데 이상하게 프록시 객체의 @Id를 접근하려 하면 또 쿼리가 필요없이 값을 얻어오더라..