-
10. 즉시 로딩과 지연 로딩웹개발/Hibernate(JPA) 2020. 4. 28. 02:31
안녕하세요 현우입니다. 이번 포스팅은 [ JPA에서 사용되는 프록시 개념 ] 입니다.
JPA 학습에 도움을 주신 김영한 개발자님과 김성인 개발자님에게 항상 감사드립니다 :)
참고서 http://acornpub.co.kr/book/jpa-programmig
자바 ORM 표준 JPA 프로그래밍
JPA 기초 이론과 핵심 원리, 그리고 실무에 필요한 성능 최적화 방법까지 JPA에 대한 모든 것
www.acornpub.co.kr
해당 포스트를 읽기전에 프록시에대한 개념이 부족하거나 이해가 잘 안가시는 분들은 이전 게시글을 참고해 주시기 바랍니다 :)
9. 프록시와 연관관계
안녕하세요 현우입니다. 이번 포스팅은 [ JPA에서 사용되는 프록시 개념 ] 입니다. JPA 학습에 도움을 주신 김영한 개발자님과 김성인 개발자님에게 항상 감사드립니다 :) 참고서 http://acornpub.co.kr/book/jpa-..
friends-aihaja.tistory.com
Member를 조회할 때 Team도 함께 조회해야 할까?
1. 지연로딩(LAZY loading)
멤버를 조회할때 연관관계가 걸려있는 팀까지 같이 Join해서 가져오게 되면 당연히 손해다. 이를 해결하기위해 JPA에서는 프록시를 사용한 지연로딩을 지원 한다.
Team에게 프록시 객체를 준다 FetchType.LAZY : 멤버 클래스만 디비에서 조회한다.
쿼리를 보면 join 없이 member만 가져오는 것을 확인 할 수 있다. 그리고 m.getTeam().getClass() 를 찍어보면 뭐가 나올까?
Proxy 멤버만 조회하고 Team은 프록시로 가져온다. 즉 m.getTeam().getName()으로 Team의 정보를 직접 조회할 시에만 select 쿼리문이 나가게 된다.
조회한 후에 select 쿼리문이 나간다 연관관계가 되어있는 클래스를 지연로딩으로 세팅시 프록시로 값을 가져오게 되는 것 이다.
member을 조회시에 연관관계 매핑이 되어있는 Team이 LAZY 즉 지연로딩 설정을 해놨다면 가짜 프록시 객체로 TEAM을 조회하게 된다 그 후 실제 team.getName() 과 같이 team을 사용시에만 쿼리문이 나간다.
2. 즉시로딩
그런데 만약 Member를 조회할때 Team도 같이 사용할 경우 어떻게 해야될까? 지연로딩을 할경우 네트워크를 두번타게 되며 이는 성능상 손해를 볼 수 밖에 없다. 이럴경우 EAGER를 사용하여 같이 조회할 수 있다.
- FetchType.EAGER
JOIN을 사용하여 한방쿼리로 전부 가져온다. 즉시 로딩의 경우 실제 가져온 값을 사용하기 때문에 프록시를 사용하지않는다.
** 중요
3. 프록시와 즉시로딩 주의
실무에서는 가급적 지연로딩을 사용하며 즉시로딩은 사용하면 안된다! 한번에 JOIN쿼리를 사용해서 값을 가져오면 좋을것 같은데 왜 쓰지 말라고 하는걸까?...
1) 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생한다. ( 특히 실무에서 x )
예시)
DB파트에서 우리가 작성한 로직을 사용할때 JOIN 쿼리문이 너무 많이 나가고 속도가 느리다고 연락이 왔다. 우리는 로직을 뒤지면서 member.getid() 조회만 했는데 왜 이렇게 성능이 느리지? 하고 생각해본다... 아차 연관관계에 EAGER을 사용했구나!! 하지만 JOIN 쿼리문이 몇개 나간다고해서 크게 성능에 문제가 생기진 않는다. 그것이 한 두개라면!!!! 매핑이 5개가 더걸린다고 생각해보면 JOIN 5개가 나간다고 생각하면 된다. 테이블이 10개라면? 10개의 테이블을 전부 끌고오는 것 이다... 실제 테이블이 많은 실무에서는 전부 지연로딩을 써야된다!
2) 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
해당 JPQL쿼리문을 실행하면 어떤 결과가 나올까?
쿼리가 두번 나간다 member, team... 왜일까? EAGER로 설정했는데? em.find()는 pk를 찍어서 가져오기 때문에 JPA에서 내부적으로 설정이 가능하다. 하지만 JPQL은 SQL문을 그대로 번역한다 위의 SQL문을 번역하면 어떻게되는가? 당연히 member만 조회 하게 된다. 즉 EAGER로 위의 쿼리문을 실행할경우 member값이 10개라면 team을 가져오기 위해 즉시! 10개의 team SQL문도 나가게 된다 !
일단 select * from member; 로 멤버 값을 가져오고 EAGER로 세팅된걸 확인한 후 select * from team where Team_id = xxx 의 쿼리문이 나가는 것 이다. TEAM이 다를경우 영속성 컨텍스트 안에도 없기때문에 더 많은 SQL 문이 나가게된다.
n+1 은 최초 쿼리문 select * from member; 을 날렸을 경우 n개의 쿼리문이 더 나간다는 말이다.
LAZY로 잡을경우 member하나만 조회하게 된다. 우선적으로 모든연관관계를 지연로딩으로 설정한 후에 member을 가져올때는 member만 가져오고 team과 함께 가져와야 할경우에는 fetchJoin을 사용해야 한다.
지연로딩으로 설정해 놨지만 fetch join을 사용하여 한방쿼리로 날려준다.
3. ManyToOne, OneToOne는 기본이 즉시로딩으로 설정되어 있으므로 지연로딩(LAZY)로 바꿔준다.
default OneToMany, ManyToMany는 기본이 지연로딩 이다.
4. 지연로딩, 즉시로딩 활용 ( 실무에서는 반드시 지연로딩 )
- Member와 Team은 자주 함께 사용 -> 즉시로딩
- Member와 Team은 가끔 함께 사용 -> 지연로딩
- Order와 Product는 자주 함께 사용 -> 즉시로딩
- Member1을 조회할 경우 EAGER로 설정된 TEAM은 같이 조회된다.
- Member1을 조회할 경우 LAZY로 설정된 orders는 프록시 객체를 생성한다.
결론
- 모든 연관관계에 지연로딩을 사용.
- 실무에서 즉시로딩을 사용하지 말자
- JPQL fetch조인이나, 엔티티 그래프 기능을 사용하자.
- 즉시로딩은 상상하지 못한 쿼리가 나간다.
'웹개발 > Hibernate(JPA)' 카테고리의 다른 글
12. 데이터 타입 분류 (0) 2020.05.01 11. 영속성 전이(CASCADE)와 고아 객체 (0) 2020.04.28 9. 프록시와 연관관계 (0) 2020.04.25 7. 고급매핑 (0) 2020.04.20 6. [JPA] 다양한 연관 관계매핑 방법 (0) 2020.04.10