JPA

즉시 로딩 & 지연 로딩

hongdangmoo 2025. 2. 26. 17:34

-> Member 엔티티와 Team 엔티티가 다대일 연관관계인 경우 Member를 조회할 때 반드시 Team 조회를 해야 할까?

-> 만약 Member만 조회하는 경우가 더 많다면 Member를 조회할 떄 굳이 사용하지도 않는 Team을 조회할 필요가 없다.

이 때 지연 로딩을 사용하여 필요한 시점에만 연관된 엔티티를 조회하도록 한다.

 

◎ 지연 로딩(Lazy)

-> 지연 로딩은 필요한 시점에 연관된 객체의 데이터를 불러오는 것이다.

-> Member와 Team이 연관되어 있는 상황에서 Member를 조회할 때 Team을 같이 조회하지 않고 필요한 시점에만 조회하려면 지연 로딩을 사용하면 된다.

Member

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는 기본이 지연 로딩이다.

 

★ 참고

자바 ORM 표준 JPA 프로그래밍 - 기본편