포스트

2023-07-18-TIL

Today I Learned

org.hibernate.LazyInitializationException: could not initialize proxy

@Transactional이 없으면 해당 에러가 발생한다.

JPA에서 연관 엔티티를 LAZY로 매핑하면 Hibernate는 처음에 진짜 객체 대신 프록시를 넣어 둔다. 프록시는 필드에 처음 접근할 때 DB를 조회해서 자기 자신을 초기화한다. 이 초기화는 엔티티가 영속성 컨텍스트(Hibernate의 Session)에 붙어 있을 때만 가능하다. Hibernate Javadoc은 이 예외를 이렇게 설명한다.

“Indicates an attempt to access unfetched data outside the context of an open stateful Session.”

열린 Session 바깥에서 아직 가져오지 않은 데이터에 접근하려 했다는 뜻이다. Spring에서는 보통 @Transactional이 붙은 메서드가 끝날 때 트랜잭션과 영속성 컨텍스트가 함께 닫힌다. 그래서 트랜잭션 없이 조회한 엔티티나, 트랜잭션이 끝난 뒤 컨트롤러나 테스트 코드에서 지연 로딩 필드를 건드리면 이 예외가 난다. @Transactional을 붙이면 해결되는 이유는 접근하는 시점까지 Session이 열려 있기 때문이다.

마지막 링크는 테스트 코드에서 getOne이나 getById를 쓸 때 같은 예외가 나는 경우를 다룬다. 두 메서드는 엔티티를 바로 조회하지 않고 참조(프록시)를 돌려준다. Spring Data JPA Javadoc을 보면 둘 다 deprecated 되었고 2.7부터 getReferenceById를 쓰라고 안내한다. 이름이 바뀌었을 뿐 프록시를 돌려준다는 성질은 같아서, 트랜잭션 밖에서 필드에 접근하면 똑같이 실패한다.

해결 방향은 크게 두 가지다.

  • 필요한 연관 데이터를 트랜잭션 안에서 미리 가져온다. Hibernate 사용자 가이드는 영속성 컨텍스트를 닫기 전에 필요한 연관을 모두 가져오는 방법을 가장 좋은 처리로 꼽고, @ManyToOne, @OneToOne에는 JOIN FETCH를 권한다.
  • 서비스 계층 메서드에 @Transactional을 붙여서, 지연 로딩이 일어나는 코드가 트랜잭션 경계 안에 있도록 한다.

트랜잭션 밖에서도 지연 로딩을 허용하는 hibernate.enable_lazy_load_no_trans 설정도 있다. 같은 가이드는 이 설정이 트랜잭션 격리를 깨기 쉬워서 권장하지 않고, 기본값이 꺼져 있다고 설명한다.

  • https://www.baeldung.com/hibernate-initialize-proxy-exception
  • https://cantcoding.tistory.com/78
  • https://zzang9ha.tistory.com/406
  • https://stackoverflow.com/questions/21574236/how-to-fix-org-hibernate-lazyinitializationexception-could-not-initialize-prox
  • https://velog.io/@ohjinseo/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C%EC%97%90%EC%84%9C-getOne-%EB%98%90%EB%8A%94-getById%EC%93%B8-%EC%8B%9C-LazyInitializationException-could-not-initialize-proxy-no-Session
이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다