DB 접근/JPA ( Java Persistence API )

지연로딩, 즉시로딩

JABHACK 2025. 1. 15. 12:42

Lazy Loading

📌 지연 로딩(Lazy Loading)은 데이터를 실제로 사용할 때 데이터베이스에서 조회하는 방식

 

JPA의 지연로딩

  • fetch 속성 사용
    • FetchType.LAZY : 지연로딩
  • 지연로딩을 사용하면 Proxy 객체를 조회한다.
  • 연관된 객체(Company)를 매번 함께 조회하는것은 낭비인 경우에 사용한다.
@Entity
@Table(name = "tutor")
public class Tutor {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
		
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "company_id")
    private Company company;

    public Tutor() {
    }

    public Tutor(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }
}
public class FetchTypeLazyMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("entity");

        EntityManager em = emf.createEntityManager();

        EntityTransaction transaction = em.getTransaction();

        transaction.begin();

        try {

            Company company = new Company("sparta");
            em.persist(company);

            Tutor tutor = new Tutor("wonuk");
            tutor.setCompany(company);
            em.persist(tutor);

            // 영속성 컨텍스트 초기화
            em.flush();
            em.clear();

            // em.find()
            Tutor findTutor = em.find(Tutor.class, tutor.getId());

            transaction.commit();
        } catch (Exception e) {
            transaction.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }

        emf.close();
    }
}

Tutor만 조회한다.

 

getCompany()

System.out.println("findTutor.getCompany().getClass() = " + findTutor.getCompany().getClass());

 

실행결과

Proxy로 조회한다.

 

getCompany().getName()

System.out.println("findTutor.getCompany().getName() = " + findTutor.getCompany().getName());

 

실행결과

  • 실제 값에 접근할 때 조회 SQL이 실행된다.
  • 실제 Company 의 값을 사용하는 시점에 초기화(DB 조회)된다.
  • 지연 로딩을 사용하면 연관된 객체를 Proxy로 조회한다.

 

Eager Loading

📌 즉시 로딩(Eager Loading)은 엔티티를 조회할 때 연관된 데이터까지 모두 한 번에 로드하는 방식

 

  • JPA의 즉시 로딩
    • fetch 속성 사용
      • FetchType.EAGER : 즉시 로딩
    • Proxy 객체를 조회하지 않고 한 번에 연관된 객체까지 조회한다.
    • 연관된 객체(Company)를 매번 함께 조회하는것이 효율적인 경우에 사용한다.
@Entity
@Table(name = "tutor")
public class Tutor {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "company_id")
    private Company company;

    public Tutor() {
    }

    public Tutor(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }
}
public class FetchTypeEagerMain {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("entity");

        EntityManager em = emf.createEntityManager();

        EntityTransaction transaction = em.getTransaction();

        transaction.begin();

        try {

            Company company = new Company("sparta");
            em.persist(company);

            Tutor tutor = new Tutor("wonuk");
            tutor.setCompany(company);
            em.persist(tutor);

            // 영속성 컨텍스트 초기화
            em.flush();
            em.clear();

            // em.find()
            Tutor findTutor = em.find(Tutor.class, tutor.getId());

            // getCompany()
            System.out.println("findTutor.getCompany().getClass() = " + findTutor.getCompany().getClass());

            // getCompany().getName()
            System.out.println("findTutor.getCompany().getName() = " + findTutor.getCompany().getName());

            transaction.commit();
        } catch (Exception e) {
            transaction.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }

        emf.close();
    }
    
}

JOIN을 사용해 한번의 SQL로 모두 조회하기 때문에 Proxy가 필요없다.

 

 

즉시 로딩 주의점

 

 

코드 예시(N+1 문제)

List<Tutor> tutorList = em.createQuery("select t from Tutor t", Tutor.class).getResultList();

 

실행결과

  • 조회 SQL이 N+1번 실행된다.
    • 처음 실행된 최초 SQL Query : 1(Tutor)
    • 연관된 객체 조회 SQL Query : N(Company)
  • JPQL은 SQL이 그대로 변환되어 조회된 Tutor 만큼 EAGER로 설정된 Company가 함께 조회된다.
  • em.find() 는 JPA가 내부적으로 최적화한다.

N+1 문제 해결 방법

  • 모든 연관관계를 LAZY로 설정한다.
  1. JPQL fetch join : Rumtime에 원하는 Entity를 함께 조회할 수 있다.(대부분 사용)
  2. @EntityGraph
  3. @BatchSize
  4. Native Query

 

'DB 접근 > JPA ( Java Persistence API )' 카테고리의 다른 글

JPA와 Transaction  (0) 2025.01.17
Proxy  (0) 2025.01.14
상속관계 매핑  (0) 2025.01.13
연관관계  (0) 2025.01.12
[JPA] Spring Data JPA  (0) 2025.01.07