즉시 로딩 & 지연 로딩
-> Member 엔티티와 Team 엔티티가 다대일 연관관계인 경우 Member를 조회할 때 반드시 Team 조회를 해야 할까?
-> 만약 Member만 조회하는 경우가 더 많다면 Member를 조회할 떄 굳이 사용하지도 않는 Team을 조회할 필요가 없다.
이 때 지연 로딩을 사용하여 필요한 시점에만 연관된 엔티티를 조회하도록 한다.
◎ 지연 로딩(Lazy)
-> 지연 로딩은 필요한 시점에 연관된 객체의 데이터를 불러오는 것이다.
-> Member와 Team이 연관되어 있는 상황에서 Member를 조회할 때 Team을 같이 조회하지 않고 필요한 시점에만 조회하려면 지연 로딩을 사용하면 된다.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team team = new Team();
team.setName("SSG Landers");
em.persist(team);
Member member = new Member();
member.setUsername("member");
member.setTeam(team);
em.persist(member);
// 영속성 컨텍스트 초기화
em.flush();
em.clear();
Member m = em.find(Member.class, member.getId());
System.out.println("m = " + m.getTeam().getClass());
System.out.println("=====================");
System.out.println("teamName = " + m.getTeam().getName()); // 지연로딩인 경우 이 시점에 Team에 대한 select 쿼리가 나감
System.out.println("=====================");
tx.commit(); // 커밋
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
em.close();
emf.close();
}
- 결과
- 위 코드를 실행했을 때 em.find()를 할 때 까지는 Member 엔티티만 select 쿼리가 나가고 Member와 연관관계에 있는 Team에 대한 select 쿼리는 나가지 않는다. 또한 m.getTeam().getClass()를 하면 Team 엔티티는 실제 엔티티가 아닌 프록시 엔티티임을 알 수 있다.
- Team 엔티티는 프록시 상태로 있다가 m.getTeam.getName()을 통해 Team 객체에 있는 데이터에 접근을 하면 그 때 영속성 컨텍스트에 실제 Team 객체를 요청하여 Team에 접근하여 Team에 대한 select 쿼리가 나가게 된다.
-> 이렇듯 Member와 Team이 함께 사용되는 경우가 적다면 지연 로딩을 통해 Member에만 접근할 때는 Team에 대한 쿼리가 나가지 않도록 하여 성능을 향상할 수 있다.
-> 만약 Member와 Team을 함께 사용해야 하는 경우가 많다면 이 때는 즉시 로딩을 사용한다.
◎ 즉시 로딩(Eager)
-> 데이터를 조회할 때 연관관계에 있는 데이터도 조회한다.
- 결과
- em.find()를 하면 Member와 함께 Team도 조회가 된다.
- 또한 Team 클래스가 프록시가 아니라 실제 엔티티를 사용한 것을 알 수 있다.
◎ 즉시 로딩 주의 사항
-> 실무에서는 가급적으로 지연 로딩만 사용하도록 한다.
-> 즉시 로딩을 사용할 경우 조회하는 엔티티와 연관된 엔티티의 쿼리까지 실행되기 때문에 예상하지 못한 SQL이 발생할 수 있다.
-> 즉시 로딩은 JPQL에서 *N + 1 문제를 일으킨다.
*N + 1 문제 : 한 번의 쿼리로 해결할 수 있는 것을 연관된 엔티티 등의 요인으로 여러 번 데이터를 불필요하게 조회하는 문제
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
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();
// 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
/*
JOQL에서는 아래 쿼리가 그대로 SQL로 번역이 된다. 이러면 Member를 select한다.
그런데 Member엔티티에서 연관관게로 지정된 Team이 즉시 로딩으로 지정되어있기 떄문에 Team과 관련된 쿼리까지 나간다.
Member의 개수가 많아지면 그 만큼 Team에 대한 쿼리가 나가게 되어 성능 저하가 발생한다.
*/
List<Member> members = em.createQuery("select m from Member m", Member.class)
.getResultList();
tx.commit();
- 결과
- em.createQuery를 통해 쿼리를 작성하면 해당 쿼리를 JPQL에서 그대로 SQL로 번역한다. 이 과정에서 select Member를 했기 떄문에 Member를 select하는 쿼리가 나가고 Member와 연관된 Team까지 select 쿼리가 나가게 된다.
- 만약 Member를 10번 조회한다고 하면 그만큼 Team 엔티티도 추가로 조회가 되는 것이다.
-> @ManyToOne, @OneToOne은 기본 설정이 Eager로 즉시로딩이다. 따라서 이 애너테이션들을 사용할 때는 Lazy로 설정을 해주어야 한다.
-> @OneToMany, @ManyToMany는 기본이 지연 로딩이다.
★ 참고