-
[Spring JPA] JPA에서 Lazy loading 은 도대체 어떻게 이루어지는 걸까?Back End/Spring Data JPA 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
2. Transaction Management
728x90'Back End > Spring Data JPA' 카테고리의 다른 글
[Spring JPA] 성능 개선을 하기 위해 당신이 알아야 할 상식 (0) 2022.01.01 [Spring JPA] 트랜잭션 로깅하기, 테스트 실행환경 분리하기 (yml, properties) (0) 2021.12.24 [Spring JPA] Spring Test 에서 Auditing 이 안될때 (@DataJpaTest) (0) 2021.12.24 [Spring JPA] 왜 테스트코드와 실제 어플리케이션코드가 실행이 다르게 될까? (0) 2021.12.24 [Spring JPA] JPA의 사실과 오해 (feat. NHN Cloud) (0) 2021.12.23