[트러블 슈팅]LazyInitializationException 간단 해결법
오류 발견
아래 코드를 테스트하던 중 LazyInitializationException이 발생했습니다.
public GrammarBookResponseDto getUserWrongGrammarBook(String userName, String grammarBookName) { Long userId = getUserIdByUserName(userName); List<UserGrammarStatus> userGrammarStatuses = this.userGrammarStatusRepository.findByUserId(userId); Long grammarBookId = this.grammarBookService.getGrammarBookIdByGrammarBookName(grammarBookName); List<GrammarDto> grammarDtos = new ArrayList<>(); for (UserGrammarStatus userGrammarStatus : userGrammarStatuses) { if (userGrammarStatus.getGrammarBookId() != null && userGrammarStatus.getGrammarBookId().equals(grammarBookId)) { grammarDtos.add(this.grammarService.getGrammar(userGrammarStatus.getWrongGrammarId())); } } return GrammarBookResponseDto.builder() .id(grammarBookId) .name(grammarBookName) .grammars(grammarDtos) .build(); }
오류 로그는 다음과 같습니다.
org.hibernate.LazyInitializationException: could not initialize proxy [com.wordwave.grammarbook.GrammarBook#14] - no Session at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:164) at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:310) at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:44) at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:102) at com.wordwave.grammarbook.GrammarBook$HibernateProxy$a5pHvLS3.getName(Unknown Source) at com.wordwave.grammar.GrammarService.getGrammar(GrammarService.java:29) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:352) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:713) at com.wordwave.grammar.GrammarService$$SpringCGLIB$$0.getGrammar(<generated>) at com.wordwave.grammar.UserGrammarStatusService.getUserWrongGrammarBook(UserGrammarStatusService.java:63) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:352) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:713) at com.wordwave.grammar.UserGrammarStatusService$$SpringCGLIB$$0.getUserWrongGrammarBook(<generated>) at com.wordwave.grammar.UserGrammarStatusServiceTest.getUserWrongGrammarBookTest(UserGrammarStatusServiceTest.java:46) ...
오류 원인
org.hibernate.LazyInitializationException
은 준영속 상태의 프록시를 초기화하면 발생합니다.
준영속(detached) 상태란, 엔티티 객체가 영속성 컨텍스트에 있다가 분리된 상태입니다. 준영속 상태의 엔티티 객체는 영속성 컨텍스트의 기능을 사용할 수 없습니다.
프록시 초기화란, 영속 상태의 프록시 객체의 메서드를 이용하려 할때 프록시 객체가 영속성 컨텍스트에 요청해 실제 엔티티 객체를 데이터베이스에서 불러와 참조하는 것입니다.
for문 안에서 getGrammar()
를 여러 번 반복하는 동안, 동일한 GrammarBook과 연관관계를 맺고 있는 여러 Grammar가 존재할 수 있습니다.
만약 getGrammar()
가 호출되어 GrammarBook에 접근하여 grammarBookName을 가져오기 위해 GrammarBook 프록시 객체를 초기화하고, 필요한 데이터를 가져온뒤 메서드가 종료되었다고 해봅시다. 이때 영속성 컨텍스트에서는 해당 GrammarBook 엔티티 객체와 프록시 객체는 준영속 상태가 되지만, 프록시 객체는 PK값을 갖고 있습니다.
그 뒤에 또 getGrammar()
가 호출되고, 해당 Grammar가 이전과 같은 GrammarBook에 접근한다면 JPA는 같은 PK값을 가진 프록시 객체를 먼저 이용하려고 듭니다.
그리고 getName()
으로 grammarBookName값을 가져오려고 하는데, PK값이 아닌 데이터에 접근하려면 프록시를 초기화해야 합니다. 하지만 이미 이 프록시 객체는 준영속 상태여서 영속성 컨텍스트에 프록시 초기화 요청을 하지 못합니다. 그래서 org.hibernate.LazyInitializationException
가 발생했다고 생각합니다.
오류 해결
문제의 메서드에 @Transactional
을 붙이면 메서드가 끝날때까지 메서드 실행중 생성되는 영속성 컨텍스트내 엔티티 객체를 제거하지 않습니다.