Framework & Library/Spring

[Spring Boot/JPA] org.hibernate.LazyInitializationException: could not initialize proxy - no Session

sechoi 2023. 6. 13. 01:54

https://sechoi.tistory.com/15

 

[Spring Boot/JPA] FetchType, Lazy Loading

데이터베이스의 두 테이블이 (일대다 혹은 다대일) 연관 관계를 맺을 때 FK를 통해 연결된다. FK는 연결 된 테이블의 PK이다. 하지만 JPA를 사용하면 코드에서 PK가 아닌 PK가 나타내는 테이블 자체

sechoi.tistory.com

tmi. 위 포스팅을 한 이유는 사실 이 포스팅을 위해서였다 🙃

 

문제 상황

@Service
public class Service {

    public List<CommentResponse> showAllCommentsFromBoard(Long boardId) {
        return freeBoardCommentCustomRepository.findAllByBoard(boardId)
                .stream().map(CommentResponse::from)
                .collect(Collectors.toList());
    }
    
}

위와 같은 서비스 코드가 있었다. 해당 메서드는 요약하자면 다음과 같은 기능을 수행한다.

  1. DB에서 댓글 entity 객체들을 조회한다.
  2. entity를 dto로 변환한다.

댓글 테이블은 게시글 테이블과 지연 로딩으로 다대일 연관관계가 존재했다. 

 

해당 코드는 컴파일 단계에서는 문제가 없지만, 메서드를 실행하는 순간 제목과 같은 에러가 발생한다.

 

문제 원인

사실 해당 exception의 이름(LazyInitializationException)이나 오류 메세지를 본 순간 원인을 짐작할 수 있다. 지연 로딩 때문이다. 댓글 entity를 조회할 때 실제 댓글 객체와 프록시 게시글 객체가 조회된다. entity를 dto로 변환할 때 게시글의 필드에 접근하므로 실제 객체가 필요했던 것이다. 하지만 왜 그 순간 실제 게시글 객체를 조회하지 않고 에러가 났을까?

 

바로 두 가지 이유 때문이다.

  1. 코드에서 @Transactional 부재
  2. 설정(application.yaml)에서 spring.jpa.open-in-view를 false로 설정

spring.jpa.open-in-view 는 OSIV라고도 하며 해당 속성을 true로 설정할 경우 영속성 컨텍스트가 transaction 범위를 벗어난 레이어까지 살아있게 된다.

출처: https://ddd4117.github.io/2021/06/jpa-osivopen-session-in-view/

하지만 나는 false로 설정해두었다. 따라서 DB에서 실제 댓글 객체 및 프록시 게시글 객체를 가져오고 repository를 벗어나는 순간 transaction이 종료되며 영속성 컨텍스트 또한 종료된 것이다. 영속성 컨텍스트가 종료된 이후에 실제 객체를 조회하려니 실패하는 것이다.

 

해결

그렇다면 transaction의 범위를 service까지 넓히거나 OSIV를 true로 설정하면 해결 된다. 후자는 영속성 컨텍스트를 종료하지 않고 계속 유지하므로 DB connection 또한 계속 유지하고 있게 된다.

따라서 service의 해당 메서드에 @Transactional(readOnly = true)를 추가해 DB 조회가 끝나도 transcation이 종료되지 않도록 했다. 

 

참고. 관련 내용을 조사하다가 Spring의 transaction에 관한 에러를 해결하는 자세한 포스팅(https://techblog.woowahan.com/2606/) 을 발견했다. 재밌고 유익해서 추가함!