Back End/Spring Data JPA

[Spring JPA] JPA에서 Lazy loading 은 도대체 어떻게 이루어지는 걸까?

DevPing9_ 2021. 12. 24. 20:51

이 글은 독자가 Lazy Loading이 무엇인지 대해 알고 있다고 가정하고 쓰여졌습니다.

 


 

가끔 분명 트랜잭션이 닫혔는데, lazy loading이 되는 당황스러운 일이 발생한다.

여태 이유를 몰랐는데 알게되어 포스팅하고자 한다.

 


 

[알아두어야 할 사실]

 1. getter 로 연관관계를 맺은 엔티티를 호출할때 쿼리문을 실행하여 불러온다.

 

 2. 엔티티가 detach 되면 해당 엔티티는 lazy loading 을 할 수 없다.

      (= 트랜잭션이 끝나면 lazy loading 을 할 수 없다.)

   

   (이 방식이 효율적이고 권장되는 방식이기에 일반적으로 알려진 사실이지만, 사실과 다르다)

 

 

 

[들어가기 전에]

# 개념정리

    [준영속 상태] - 영속상태의 엔티티가 detach 된 상태 (로딩 되지 않은 연관관계는 proxy 상태에 있다)

    [비영속 상태] - 영속상태가 된적이 없는 엔티티의 상태

 

    [프록시 객체의 초기화]

    아직 DB에서 불러오지 않은 연관관계는 proxy 객체(가짜)로 관리된다.

    getter가 호출 됬을 때, 프록시를 초기화하면서 원래 객체로 만든다. 

     

    [트랜잭션과 EntityManager 의 라이프사이클]

    일반적으로 트랜잭션과 EntityManager의 생명주기는 같으며, 트랜잭션이 주체이다.

 

 

[좀 더 자세하게]

    [엔티티가 detach 되는 시점에 발생하는 일]

    트랜잭션이 닫히는 시점 ( 트랜잭션이 cleanupAfterCompletion() 이 호출할 때 )

 

    SessionFactoryOptions.isInitializeLazyStateOutsideTransactionsEnabled()  False 일때

    알려진 것과 같이 해당 엔티티는 detach 되면 lazy loading 이 불가능하다.

 

    해당 옵션이 True 이면 lazy initializer 에게 참조 정보를 저장해 놓는다.

 

    이 때 준영속 상태의 엔티티가 연관관계를 호출하면 트랜잭션이 새로 열리면서

    저장된 참조 정보에 따라 해당 엔티티를 관리하던 EntityMangerFactory를 생성하고

    EntityManger 를 만들어 프록시를 초기화 한다.

   

    이리하여 lazy loading 이 서로 다른 트랜잭션에서 가능하게 된다.

    

   [옵션 설정하기]

    yml 파일이나 properties 에서 hibernate.enable_lazy_load_no_trans 옵션을 통해 조작할 수 있다.

 

   [해당 옵션을 True 로 주었을 때 단점] - 안티패턴

   프록시를 초기화할 때 마다 새로운 EntityManger 와 트랜잭션이 생긴다.

   트랜잭션이 열리고 닫히면, JDBC 커넥션도 열리고 닫히기에 리소스 낭비가 된다.

 

   어쩔 수 없이 꼭 사용해야된다면 OSIV 사용을 권장한다. (OSIV 는 커넥션이 지속된다)

   

   * 따라서, 프록시 객체의 초기화가 필요하다면 트랜잭션이 닫히기전에 초기화를 해주자. 

 

 

 

  [알아두면 좋은 점] 

  * Getter에 의한 lazy loading 은 N+1 문제로 사용권장되지 않는다.

 

  * 연관관계가 필요한 필드들만 모아 Fetch Join 쿼리를 만들어 서비스 단에서 제공하는 것이 좋다. 

 


 

# Reference

1. Entity-Proxy Initialization

 

Hibernate Detached 엔티티 프록시의 초기화

문제 상황 Spring + Hibernate + Spring Data JPA 환경에서 다음과 같은 상황이 있었다. 123456789fun doSomeTask(transactionTemplate: TransactionTemplate, entityRepository: EntityRepository) { val entity = transactionTemplate.execute { entityR

suhwan.dev

2. Transaction Management

 

Spring Transaction 사용 시 주의할 점

개요 최근 몇 달 간 내가 Spring에서 트랜잭션을 사용할 때 겪었던 여러 문제 상황에 대해서 이야기하려고 한다. 트랜잭션 안에서 트랜잭션을 새로 여는 경우 트랜잭션 안에서 새로운 트랜잭션을

suhwan.dev

 

728x90