qwerasdfzxc
165
2020-07-26 23:09:37
6
520

LazyLoading && Transactional 문제 질문드립니다.


현재 1:N관계의 FetchType에 대해 공부중입니다.

테스트 코드를 작성하던 중 

failed to lazily initialize a collection of role: could not initialize proxy - no Session 오류가 발생하였고,

@Transcational을 붙여주었더니 정상적을 작동이 되었습니다.


@Test
@Transactional
fun scrapTest() {
postService.scrapPost("20202020", existId)
val post = postRepository.findById(existId).orElseThrow { PostNotExistException() }

Assertions.assertNotEquals(0, post.scrapUser.size)
}

코드는 이렇습니다.

@Transactional의 해결법을 찾기전에, 어떤 블로그에서 JPA에서 get과 find의 차이점에 대한 글을

읽어보게 되었는데, 그 글을 읽고 find는 entity를 리턴하고 get은 프록시를 리턴하기 때문에,

프록시가 아니면  lazyLoading을 할 수없어서 이런 일이 발생하는 거라고 추측하게되었습니다.


그런데 test코드가 아닌 다른 코드에서 find로 찾은 lazyLoading 컬렉션이 정상 작동이 되어 혼란에 빠지게 되었습니다..

fun findUserRelatedPost(userId: Long, type: PostSearch): List<ResponsePostsDto> {
val user = userRepository.findById(userId).orElseThrow { UserNotExistsException() }
return user.scrapPost.map { ResponsePostsDto(it) }

}

혹시 하나의 코드는 오류가 뜨고 하나의 코드는 정상작동되는 이유는 @Transactional의 이유 때문인가 하고 찾아본 결과 Transactional 에노테이션이 트랜잭션을 관리해준다라고만 뜰분 이해할만한 사항을 찾지못했습니다...


말을 너무 중구난방으로 쓴 것 같은데.. 양해부탁드립니다 ㅠㅠ..

1
  • 답변 6

  • doubler
    470
    2020-07-26 23:40:49 작성 2020-07-26 23:41:50 수정됨

    안녕하세요. 제가 알기론 우선적으로 find 와 get 은 차이점이 딱히 없다고 생각합니다. 단순 JpaRepository 를 통한 쿼리메소드 네이밍을 find 로 할건지 또는 get 으로 할건지 그 차이 밖에 없다고 생각합니다.


    @Transactioal 을 붙였을 때 lazyloading 이 되는 부분은 find 와 get 과는 별개의 이야기입니다.

    가령 우리가 어떤 작업을 행한다고 행하는데 DB 와의 접촉(연결) 이 필요한 경우 아래와 같이 동작합니다.

    트랜잭션 열기 ---> 작업 수행 ---> 트랜잭션 닫기 및 커밋


    여기서 @Transacational 을 붙여주신건 scrapTest() 메소드를 하나의 작업단위로 보겠다는 의미입니다. 그러면 Jpa 상에서 영속성 컨텍스트에 의해서 postRepository.findById() 수행시 조회된 엔티티 post 는 DB 내에서 영속성 컨텍스트로 들어오게 되어있고, 거기서 연관관계가 맺혀져 있는 user 의 경우에는 프록시로 잡힙니다. 그리고 해당 user 를 조회할 시에 프록시 객체가 실제 user entity 형태로 변화되면서 lazyLoading 으로 select 쿼리가 날라가는데 이 때 날라가는 이유가  @Transactional 에 의해서 영속성 컨텍스트로 지금 post 가 관리되고 있기 때문입니다.


    JpaRepository 는 매우 추상화가 잘되어있는 인터페이스입니다. 그 내면에는 하이버네이트란 구현체가 작동하고 있는데 그 속에 있는 entityManagerFactory 랑 entityManager 도 같이 공부해보시면 도움이 될 듯합니다.


    에시로 들어주신 건 코틀린 같은데 저는 스프링부트를 이용하고 있고, 저 같은 경우 레파지토리 테스트시 스프링부트에서 제공하는 애노테이션인 @DataJpaTest 를 이용합니다. 

  • qwerasdfzxc
    165
    2020-07-26 23:48:47

    @doubler님 

    ㅠㅠㅠ 정말 친절한 설명 감사드립니다.

    꼭 entityManager에 대해서 알아봐야 할 것같습니다.


    그렇다면 밑의 코드가 작동하는건 밑의코드는 자동으로 메소드 단위로 Transaction이 적용이 된 것...인가요??


    두 코드의 생김새가 별 차이가 없어보이는데 이런 결과를 내는건 단순 test코드와 일반 코드의 차이인지 궁금합니다 ㅠㅠ

  • doubler
    470
    2020-07-27 00:02:43

    메소드 블럭만 봐서는 잘 모르겠는데, 하위의 예시에는 트랜잭셔널이 적용되어 있으니 lazyLoading 이 되는듯 하네요.

  • 봄꾸
    1k
    2020-07-27 08:24:25 작성 2020-07-27 08:26:21 수정됨

    위에 분이 답을 잘 해주셔서 몇 가지만 적어봅니다.

    일반적으로 스프링 osiv가 켜져 있는 경우(default) 트랜잭션이 종료되어도 영속성 컨텍스트가 응답을 내보내기 전 까지 열려있기 때문에 정상 작동할 것입니다.

    글쓴이님이 osiv를 꺼두신 상태이신 것 같은데 osiv를 끄시면 기본적으로 영속성 컨텍스트는 트랜잭션과 수명을 함께합니다.

    즉 영속성 컨텍스트가 닫혀 있다면 lazy 로딩이 불가능 하다는 것입니다.

    그리고 말씀하신 엔티티가 아닌 프록시를 얻는 메소드는 entitymanager의 getReference 메소드를 말씀하시는 것 같습니다. 마찬가지로 영속성 컨텍스트가 닫혀 있다면 lazy 로딩 시 익셉션이 발생합니다

  • 어쩌다프로그래머
    6k
    2020-07-27 10:46:45

    음 그러면 lazyLoading 이 가능할려면 

    반드시 @Transactioal 을 선언해야 하는건가요..?

    아니면 @Transactioal 선언 없이도 특정경우에만 발생하는건가요?

  • qwerasdfzxc
    165
    2020-07-27 12:40:43 작성 2020-07-27 12:47:03 수정됨

    혹시 test코드는 osiv에서 뷰영역에 포함되는건가요??

    Osiv의 경우 Springboot에서 default로 켜준다고하는데

    현재 SpringBoot사용중입니다. 이것또한 test코드들ㅇ은 springboot외의 영역인건가요?

  • 로그인을 하시면 답변을 등록할 수 있습니다.