즉시로딩 지연로딩
- 지연로딩를을 사용하는 이유?
- 만약 Member 엔티티에 Team이라는 엔티티가 속해 있는데 Member 엔티티를 조회할 때마다 Team엔티티도 함께 조회해야만 할까?
- 답은 비즈니스 로직에서 필요하지 않을 때가 있는데 항상 Team을 함께 가져와서 사용할 필요는 없다
- JPA는 낭비를 하지 않기 위해서 지연로딩을 사용한다
코드
- Member와 Team 사이가 @ManyToOne 관계로 매핑되어 있는 상황에서, 어노테이션에 fetch 타입을 줄 수 있다
- FetchType.LAZY
// 패치 타입 LAZY 설정
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id", insertable = false, updatable = false)
private Team team;
- Member를 조회하고, Team 객체의 클래스를 확인해보면 Proxy 객체가 조회된다
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("memberA");
em.persist(member);
member.changeTeam(team);
em.flush();
em.clear();
Member findMember = em.find(Member.class, member.getId());
System.out.println(findMember.getTeam().getClass());\\
Hibernate:
select
member0_.id as id1_4_0_,
member0_.createdBy as createdB2_4_0_,
member0_.createdDate as createdD3_4_0_,
member0_.lastModifiedBy as lastModi4_4_0_,
member0_.lastModifiedDate as lastModi5_4_0_,
member0_.age as age6_4_0_,
member0_.description as descript7_4_0_,
member0_.locker_id as locker_10_4_0_,
member0_.roleType as roleType8_4_0_,
member0_.team_id as team_id11_4_0_,
member0_.name as name9_4_0_,
locker1_.id as id1_3_1_,
locker1_.name as name2_3_1_
from
Member member0_
left outer join
Locker locker1_
on member0_.locker_id=locker1_.id
where
member0_.id=?
class hello.jpa.Team$HibernateProxy$e97rdqZR // 프록시 객체
- 지연로딩을 설정해 놓았던 Team을 출력해보면
- 실제로 팀 객체의 조회가 필요한 시점에 쿼리가 나간다
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("memberA");
em.persist(member);
member.changeTeam(team);
em.flush();
em.clear();
Member findMember = em.find(Member.class, member.getId());
System.out.println(findMember.getTeam().getClass());
System.out.println("TEAM NAME : " + findMember.getTeam().getName());
Hibernate:
select
member0_.id as id1_4_0_,
member0_.createdBy as createdB2_4_0_,
member0_.createdDate as createdD3_4_0_,
member0_.lastModifiedBy as lastModi4_4_0_,
member0_.lastModifiedDate as lastModi5_4_0_,
member0_.age as age6_4_0_,
member0_.description as descript7_4_0_,
member0_.locker_id as locker_10_4_0_,
member0_.roleType as roleType8_4_0_,
member0_.team_id as team_id11_4_0_,
member0_.name as name9_4_0_,
locker1_.id as id1_3_1_,
locker1_.name as name2_3_1_
from
Member member0_
left outer join
Locker locker1_
on member0_.locker_id=locker1_.id
where
member0_.id=?
class hello.jpa.Team$HibernateProxy$z4JtUeLD // 프록시 객체
Hibernate:
select
team0_.id as id1_8_0_,
team0_.createdBy as createdB2_8_0_,
team0_.createdDate as createdD3_8_0_,
team0_.lastModifiedBy as lastModi4_8_0_,
team0_.lastModifiedDate as lastModi5_8_0_,
team0_.name as name6_8_0_
from
Team team0_
where
team0_.id=?
TEAM NAME : teamA
- 지연로딩이란
- 로딩되는 시점에 Lazy 로딩이 설정 되어있는 Team 엔티티는 프록시 객체로 가져온다
- 후에 실제 객체를 사용하는 시점에 초기화가 된다. DB 쿼리가 나간다
- 대부분 비즈니스 로직에서 Member와 Team 을 같이 사용한다면
- 이런경우는 LAZY 로딩을 사용한다면, SELECT 쿼리가 따로따로 2번 나간다
- 네트워크를 2번 타서 조회가 이루어져 손해가 생긴다
- 이때는 즉시로딩을 사용한다
- 즉시로딩이란?
- fetch 타입을 EAGER로 설정하면 된다
@ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "team_id", insertable = false, updatable = false) private Team team;
Team team = new Team(); team.setName("teamA"); em.persist(team); Member member = new Member(); member.setUsername("memberA"); em.persist(member); member.changeTeam(team); em.flush(); em.clear(); Member findMember = em.find(Member.class, member.getId()); System.out.println(findMember.getTeam().getClass()); System.out.println("TEAM NAME : " + findMember.getTeam().getName()); tx.commit();
Hibernate: select member0_.id as id1_4_0_, member0_.createdBy as createdB2_4_0_, member0_.createdDate as createdD3_4_0_, member0_.lastModifiedBy as lastModi4_4_0_, member0_.lastModifiedDate as lastModi5_4_0_, member0_.age as age6_4_0_, member0_.description as descript7_4_0_, member0_.locker_id as locker_10_4_0_, member0_.roleType as roleType8_4_0_, member0_.team_id as team_id11_4_0_, member0_.name as name9_4_0_, locker1_.id as id1_3_1_, locker1_.name as name2_3_1_, team2_.id as id1_8_2_, team2_.createdBy as createdB2_8_2_, team2_.createdDate as createdD3_8_2_, team2_.lastModifiedBy as lastModi4_8_2_, team2_.lastModifiedDate as lastModi5_8_2_, team2_.name as name6_8_2_ from Member member0_ left outer join Locker locker1_ on member0_.locker_id=locker1_.id left outer join Team team2_ on member0_.team_id=team2_.id where member0_.id=? class hello.jpa.Team TEAM NAME : teamA
- 프록시와 즉시 로딩 주의할 점
- 즉시 로딩을 사용하면 예상치 못한 SQL이 발생한다
- @ManyToOne이 5개 있는데 전부 EAGER로 설정되어 있다고 생각해보면
- 조인이 5개 일어난다. 실무에서는 테이블이 더 많다
- 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다
- 실무에서 복잡한 쿼리를 많이 풀어내기 위해서 뒤에서 학습할 JPQL을 많이 사용한다
- 하지만, JPQL에선 입력 받은 query string이 그대로 SQL로 변환된다.
- "select m from Member m" 이 문장으로 당연히 Member만 SELECT 하게 된다.
- 그러니 Member를 다 가져온것이다
- 이때 문제가 생긴다 LAZY면 프록시를 넣으면 되는데, EAGER는 반환하는 시점에 다 조회가 되어 있어야 한다
- 이 말은 MEMBER를 와 연관된 TEAM을 모두 가져와야 한다는 소리다
- 이게 N+1문제이다
- 멤버가 2명이고, 팀도 2개인 코드를 만들고 모든 멤버를 조회해보자
Team team1 = new Team(); team1.setName("teamA"); em.persist(team1); Team team2 = new Team(); team2.setName("teamB"); em.persist(team2); Member member1 = new Member(); member1.setUsername("memberA"); em.persist(member1); member1.changeTeam(team1); Member member2 = new Member(); member2.setUsername("memberB"); em.persist(member2); member2.changeTeam(team2); em.flush(); em.clear(); List<Member> members = em .createQuery("select m from Member m", Member.class) .getResultList(); tx.commit();
- 실행 결과를 보면 MEMBER를 가져오는데 TEAM을 각각 쿼리 날려서 가져온다
- 실무에서 이러한 문제가 터지면 답이없다..
Hibernate: /* select m from Member m */ select member0_.id as id1_4_, member0_.createdBy as createdB2_4_, member0_.createdDate as createdD3_4_, member0_.lastModifiedBy as lastModi4_4_, member0_.lastModifiedDate as lastModi5_4_, member0_.age as age6_4_, member0_.description as descript7_4_, member0_.locker_id as locker_10_4_, member0_.roleType as roleType8_4_, member0_.team_id as team_id11_4_, member0_.name as name9_4_ from Member member0_ Hibernate: select team0_.id as id1_8_0_, team0_.createdBy as createdB2_8_0_, team0_.createdDate as createdD3_8_0_, team0_.lastModifiedBy as lastModi4_8_0_, team0_.lastModifiedDate as lastModi5_8_0_, team0_.name as name6_8_0_ from Team team0_ where team0_.id=? Hibernate: select team0_.id as id1_8_0_, team0_.createdBy as createdB2_8_0_, team0_.createdDate as createdD3_8_0_, team0_.lastModifiedBy as lastModi4_8_0_, team0_.lastModifiedDate as lastModi5_8_0_, team0_.name as name6_8_0_ from Team team0_ where team0_.id=?
- 즉시 로딩을 사용하면 예상치 못한 SQL이 발생한다
- 결론 : 실무에서는 LAZY 전략을 사용하자
- 실무에서 대부분 멤버 팀을 함께 사용하는 경우가 있는데, 이런 경우에는 JPQL의 fetch join을 통해서 해당 시점에 한방 쿼리로 가져와서 쓸 수 있다
- @ManyToOne, @OneToOne과 같이 @XXXToOne 어노테이션들은 기본이 즉시 로딩(EAGER) 이다. - 주의
이 글은 김영한님의 강의를 공부한 글입니다
'프레임워크 > JPA' 카테고리의 다른 글
[JPA] JPA 프록시를 사용하는 이유? (0) | 2022.03.23 |
---|---|
[JPA] 영속성 컨텍스트 (엔티티 매니저) (0) | 2022.03.23 |
[JPA] MappedSuperclass 란 (0) | 2022.03.23 |
[JPA] 여러가지 JPA 전략@Inheritance(strategy=InheritanceType.xxxx) (0) | 2022.03.21 |
[JPA] 일대일 [1:1] 연관관계 매핑 (0) | 2022.03.21 |
댓글