01. 지연 로딩
Member를 조회할 때 Team도 함께 조회해야 할까?
다음 그림과 같은 연관관계를 맺고 있는 엔티티가 있다고 할 때, 단순히 member 정보만 사용하는 비즈니스 로직은 println(member.getName());
이다. 이 경우, Team도 조인해서 가져오면 손실이 있다. JPA는 지연 로딩이라는 것을 제공한다. 지연 로딩을 이용하면 이 문제를 해결할 수 있다.
지연 로딩 LAZY를 이용해서 프록시로 조회하는 예시
Member 클래스의 @ManyToOne
에 속성 (fetch = FetchType.LAZY
)을 추가한다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
해당 속성을 추가하면 프록시 객체로 조회한다는 뜻이다. 즉, Member 클래스만 조회한다는 뜻이다. (Team 클래스는 조회하지 않는다!) JpaMain의 try문에서는 다음과 같이 작성하고
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);
em.flush();
em.clear();
Member m = em.find(Member.class, member1.getId());
tx.commit();
실행 쿼리를 보면, Member 클래스에 관한 것만 가져오는 것을 볼 수 있다.
Member member = em.find(Member.class, 1L);
에 해당하는 코드는 다음 그림을 보자.
새 팀을 만들어 멤버에 세팅 후 팀의 멤버가 속한 팀의 클래스를 가져오는 예시
이 경우도 지연 로딩 LAZY를 사용해서 프록시로 조회하기 때문에 Member 클래스에는 변화가 없다. 다음은 JpaMain의 try문에 포함되는 코드이다.
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member1 = new Member();
member1.setUsername("member1");
member1.setTeam(team);
em.persist(member1);
em.flush();
em.clear();
Member m = em.find(Member.class, member1.getId());
System.out.println("m = " + m.getTeam().getClass());
tx.commit();
실행 결과를 보면, member의 team이 Proxy
로 나온 것을 볼 수 있다.
Team team = member.getTeam();
와 team.getName();
에 해당하는 코드는 다음 그림을 보자.
실제 team에 있는 무언가를 사용할 때 프록시가 초기화 되는것이다. 프록시를 touch만 한다고 해서 초기화 되는 것이 아니라, 프록시에 관련된 것을 touch 할 때 초기화 된다. 따라서 Team team = member.getTeam();
에서 초기화 되는 것이 아니라, team.getName();
으로 실제 team을 사용하는 시점에 초기화가 진행되며 DB를 조회하게 된다.
02. 즉시 로딩
Member와 Team을 자주 함께 사용한다면, 즉시 로딩을 사용하는 것이 좋다.
즉시 로딩 EAGER를 사용해서 함께 조회하는 예시
즉시 로딩의 동작 과정은 다음 그림을 참고하자.
JPA 구현체는 가능하면 조인을 사용해서 SQL을 한번에 함께 조회한다.
Member 클래스의 @ManyToOne
에 속성 (fetch = FetchType.EAGER
)을 추가한다. Member 클래스는 다음과 같이 작성하면 된다.
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
JpaMain은 아까 코드와 동일하다. 실행 결과로 멤버와 팀을 조인을 하는 쿼리를 볼 수 있다. 즉시 로딩이기 때문에 프록시가 필요 없다. 따라서 진짜 객체가 출력됨을 볼 수 있다.
03. 프록시와 즉시 로딩의 주의점
주의점
1) 특히 실무에서, 가급적 지연 로딩만 사용하는 것이 좋다.
2) 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생할 수 있다.
3) 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다. (아래에서 자세히 설명하겠다.)
4) @ManyToOne
, @OneToOne
은 기본이 즉시 로딩이므로 반드시 LAZY
로 설정해야 한다.
5) @OneToMany
와 @OneToOne
은 기본이 지연 로딩이다.
즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.select m from Member m
로 Member를 먼저 가져오고, Member 클래스의 team의 EAGER
로 인해 Team을 가져오므로 결국 쿼리가 두 번 나가게 된다. 이는 성능 문제로 이어질 수 있다.
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member1 = new Member();
member1.setUsername("member1");
member1.setTeam(team);
em.persist(member1);
em.flush();
em.clear();
List members = em.createQuery("select m from Member m", Member.class)
.getResultList();
tx.commit();
04. 지연 로딩 활용
실무
1) 모든 연관관계에서 지연 로딩을 사용하자.
2) 실무에서 즉시 로딩을 사용하지 말자.
3) JPQL fetch 조인이나 엔티티 그래프 기능을 사용하자.
4) 즉시 로딩은 상상하지 못한 쿼리가 나간다.
'JPA' 카테고리의 다른 글
기본값 타입 (2) | 2024.01.14 |
---|---|
영속성 전이(CASCADE)와 고아 객체 (0) | 2024.01.14 |
프록시 (0) | 2024.01.12 |
@MappedSuperclass - 매핑 정보 상속 (0) | 2024.01.11 |
상속관계 매핑 (0) | 2024.01.08 |