개발/JPA

프록시, 즉시 로딩과 지연 로딩

Debin 2022. 3. 2.
반응형

2023. 02.07 15:40 복습 시작

 

이번에는 프록시와 즉시 로딩, 지연 로딩에 대해 알아보겠다.

객체는 객체 그래프로 연관된 객체들을 탐색한다.

그런데 객체가 DB에 저장되어 있으므로 연관된 객체를 마음껏 탐색하기는 어렵다.

JPA 구현체들은 이 문제를 해결하려고 프록시라는 기술을 사용한다.

 

프록시를 사용하면 연관된 객체를 처음부터 데이터베이스에서 조회하는 것이 아니라,

실제 사용하는 시점에 데이터베이스에서 조회할 수 있다.

하지만 자주 함께 사용하는 객체들은 조인을 사용해서 함께 조회하는 것이 효과적이다.

JPA는 즉시 로딩지연 로딩이라는 방법으로 둘을 모두 지원한다.

 

한 가지 예시를 들어보자. 

다대일 단방향 관계인 멤버와 팀이 있다.

즉 멤버는 팀을 참조하는 참조 변수가 있고, 팀은 멤버를 참조하는 변수(List)가 없다. 아래 코드를 살펴보자.

//회원과 팀 함께 출력
public void printUserAndTeam(String memberId){
    Member member = em.find(Member.class, memberId);
    Team team = member.getTeam();
    System.out.println("회원 이름 : " + member.getUsername());
    System.out.println("소속팀 : " + team.getName());
}

//회원만 함께 출력
public void printUser(String memberId){
    Member member = em.find(Member.class, memberId);
    System.out.println("회원 이름 : " + member.getUsername());
}

위 코드와 같이 연관된 엔티티들이 모두 사용되는 것이 아니다.

printUserAndTeam은 팀과 멤버를 모두 출력해야해서 두 엔티티를 모두 조회해야 한다.

그러나 printUser 메소드는 회원 엔티티만 사용하므로, em.find 로 회원 엔티티를 조회할 때 회원과 연관된 팀 엔티티(Member.team)까지 데이터베이스에서 함께 조회해 두는 것은 효율적이지 않다.

 

JPA는 이런 문제를 해결하려고 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공하는데

이것을 지연 로딩이라고 한다.

 

즉 team.getName()처럼 엔티티의 실제 값을 사용할 때 데이터베이스에서 엔티티를 조회하는 것이다.

이 방법을 사용하면 printUser 메소드는 회원 데이터만 데이터베이스에서 조회해도 된다.

프록시 

  • em.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회
  • em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

프록시 조회

프록시 특징은 다음과 같이 정리할 수 있다.

 

  • 실제 클래스를 상속받아서 만들어진다.
  • 실제 클래스와 겉모양이 같다.
  • 이론상 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다. 

프록시는 엔티티를 상속

  • 프록시 객체는 실제 객체의 참조(target)를 보관한다.
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.

프록시 위임

 

프록시 객체는 member.getName() 처럼 실제 사용될 때 데이터베이스를 조회해서 실제 엔티티 객체를 생성하는데,

이것을 프록시 객체의 초기화라고 한다.

Member member = em.getReference(Member.class, "memberId");
member.getName();

프록시 초기화 과정은 아래와 같다.

프록시 초기화

초기화 과정은 다음과 같다.

 

  1. 프록시 객체에 member.getName()을 호출해서 실제 데이터를 조회한다.
  2. 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에 실제 엔티티 생성을 요청하는데 이것을 초기화라고 한다.
  3. 영속성 컨텍스트는 데이터베이스를 조회해서 실제 엔티티 객체를 생성한다.
  4. 프록시 객체는 생성된 실제 엔티티 객체의 참조를 Member target 멤버변수에 보관한다.
  5. 프록시 객체는 실제 엔티티 객체의 getName()을 호출해서 결과를 반환한다.

프록시의 특징은 아래와 같다.

 

  • 프록시 객체는 처음 사용할 때 한 번만 초기화된다.
  • 프록시 객체를 초기화한다고 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다.
    프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능.
  • 프록시 객체는 원본 엔티티를 상속받은 객체다. 따라서 타입 체크 시 조심해야한다. ( == 보다는, instance of 사용)
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 발생한다. 즉 예외가 터진다.

즉시로딩과 지연로딩

JPA는 개발자가 연관된 엔티티의 조회 시점을 선택할 수 있도록 다음 두 가지 방법을 제공한다.

 

  • 즉시 로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.
  • 지연 로딩 : 연관된 엔티티를 실제 사용할 때 조회한다.

즉시 로딩을 사용하는 예제 코드는 아래와 같다. @ManyToOne fetch 속성을 FetchType.EAGER로 지정한다.

@Entity
 public class Member {
     @Id @GeneratedValue
     private Long id;

     @Column(name = "USERNAME")
     private String name;

     @ManyToOne(fetch = FetchType.EAGER) //**
     @JoinColumn(name = "TEAM_ID")
     private Team team;
 .. 
 }

실행되는 상황은 아래 이미지와 같다.

즉시 로딩

즉시 로딩을 하면 Member를 조회 시 항상 Team도 조회한다.

JPA 구현체는 가능하면 조인 쿼리를 사용해서 SQL 쿼리 한 번으로 두 엔티티를 모두 조회한다.

 

지연로딩을 사용하는 예제 코드는 아래와 같다. 

@Entity
 public class Member {
     @Id @GeneratedValue
     private Long id;

     @Column(name = "USERNAME")
     private String name;

     @ManyToOne(fetch = FetchType.LAZY) //이 부분만 수정.
     @JoinColumn(name = "TEAM_ID")
     private Team team;
 .. 
 }

상황은 아래와 같다.

지연 로딩 

Team team = member.getTeam(); 이 코드를 실시하면 프록시 객체가 들어간다.

team.getName() 이 코드를 실행해야 엔티티를 조회하고 사용한다.

 

참고할 주의사항은 아래와 같다.

 

  • 그냥 늘 항상 지연 로딩으로 설정하자. 글로벌 지연 로딩 전략.
  • 즉시 로딩을 사용하면 예상하지 못한 SQL이 발생
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
  • @ManyToOne, @OneToOne는 기본이 즉시 로딩 -> 지연 로딩으로 수정하자.
  • @OneToMany, @ManyToMany는 기본이 지연 로딩.

 

참고자료

자바 ORM 표준 JPA 프로그래밍 - 김영한

자바 ORM 표준 JPA 프로그래밍 강의 - https://www.inflearn.com/course/ORM-JPA-Basic/dashboard

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

2023. 02.07 16:20 복습 및 정리 마무리

 

반응형

댓글