티스토리 뷰

JPA

즉시 로딩과 지연 로딩

pyounani 2024. 6. 8. 11:11

데이터베이스와 객체 관계형 매핑(ORM)에서 데이터 로딩 방식에는 두 가지 접근 방식이 있습니다. 

바로 즉시로딩과 지연로딩입니다. 이번 글에서는 즉시 로딩과 지연 로딩이 어떤 차이점을 갖고 있는지 설명드리겠습니다. 

 

설명드리기 전에 지연 로딩을 할 때 사용되는 프록시 개념부터 정리하고 설명드리겠습니다. 

1.  프록시가 왜 필요하지?

엔티티를 조회할 때 연관된 엔티티들이 항상 사용되는 것은 아닙니다. 

아래 예시를 보겠습니다. 

// 회원과 팀 출력
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() 는 memberId로 회원, 회원과 연관된 팀의 이름도 출력하고 있습니다. 
반면, printUser() 메소드는 회원만 출력하는데 사용하고 회원과 연관된 팀은 사용하지 않고 있습니다. 

 

위처럼 회원과 팀을 함께 출력하는 경우가 많다면 DB에서 Member를 조회할 때 Team도 함께 조회하는 것이 최적화 부분에서 더 좋을 것입니다. 하지만 회원만 출력하는 경우가 더 많다면 Member만 조회하고 Team은 선택적으로 조회하는 것이 더 좋을 것입니다.  

이렇게 선택적으로 조회하는 것을 가능하게 하는 것이 바로 프록시입니다.

 

 

2. 프록시란?

EntityManager em;
em.find(Member.class, "member1");

엔티티를 조회할 때 find()를 사용했었습니다. 

앞서서 동작과정을 자세하게 설명했으니 간단하게 정리하고 넘어가겠습니다. 

 

영속성 컨텍스트에 엔티티가 없다면 데이터베이스를 조회합니다. 

find()는 엔티티를 실제 사용을 하든 사용을 하지 않든 데이터베이스를 조회하게 됩니다. 

 

 

EntityManager em;
em.getReference(Member.class, "member1");

반면, getReference()는 데이터베이스를 조회하지도 않고, 실제 엔티티 객체도 생성하지 않습니다. 대신에 프록시 객체를 반환합니다. 

 

🤔 프록시?

프록시 클래스는 실제 클래스를 상속 받아서 만들어지기 때문에 실제 클래스와 겉 모양이 같습니다. 

프록시 객체는 실제 객체에 대한 참조를 보관합니다. 

 

예시를 통해 프록시가 작동하는 방식을 살펴보겠습니다. 

Member member = em.getReference(Member.class, "member1"); // 프록시 객체 반환
member.getName();

 

1. member.getName() 처럼 실제 사용이 될 때 실제 member 객체를 조회합니다. 

2. 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에 실제 엔티티 생성을 요청하는데, 이를 초기화라고 합니다. 

3. 영속성 컨텍스트는 DB를 조회해서 실제 엔티티 객체를 생성합니다. 

4. 프록시 객체는 실제 엔티티 객체의 참조를 보관합니다. 

5. 프록시 객체는 실제 엔티티 객체의 getName()을 호출해 결과를 반환합니다. 

 

프록시의 특징을 정리해 보겠습니다.

1. 프록시 객체는 처음 사용할 때 한번만 초기화가 됩니다. 

2. 프록시 객체를 초기화한다고 프록시 객체가 실제 엔티티로 바뀌는 것은 아닙니다. 
(실제 엔티티의 참조를 보관하는 것이다)

3. 만약 영속성 컨텍스트에 찾는 엔티티가 이미 있다면 데이터베이스에 조회할 필요가 없으므로 getReference()를 호출해도 실제 엔티티를 반환한다.

 

 

3.  즉시 로딩

즉시 로딩은 필요한 데이터와 연관된 데이터를 한 번에 모두 로드하는 방식입니다. 예를 들어, 데이터베이스에서 member1 객체를 가져올 때, 그 객체와 관련된 모든 연관 데이터(team1)를 즉시 조회해서 메모리에 로드합니다.

 

JPA에서는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 실행해 조회합니다. 

이 경우 한 번의 쿼리로 두 엔티티를 모두 조회할 수 있습니다. 

 

 

4. 지연 로딩

지연 로딩은 실제로 데이터가 필요할 때까지 연관된 데이터를 로드하지 않는 방식입니다. 즉, member1 객체를 가져올 때 해당 객체의 연관 데이터(team1)를 즉시 로딩하지 않고, 프록시 team1을 반환합니다. team1이 실제 사용이 될 때 데이터베이스에서 로드합니다. 

 

5.  즉시 로딩 VS 지연 로딩

실무에서는 가급적 지연 로딩만 사용합니다.

실무에서는 대규모 데이터를 가지고 진행되기 때문에 단순히 특정 엔티티를 조회하기 위해 그 엔티티와 연관된 엔티티를 가지고 오면서 예상하지 못하는 SQL이 발생하기도 하며 이는 성능 저하의 원인이 될 수 있습니다. 

 

또한, 즉시 로딩을 사용할 때 JPQL을 이용해 컬렉션을 조회하면, 첫 번째 쿼리로 N개의 엔티티를 조회한 후 각 엔티티에 대해 추가 쿼리(N개)가 발생할 수 있는 N+1문제를 일으킵니다. 

 

👉🏻 JPA 기본 전략은  @XXXToMany는 지연로딩이고, @XXXToOne은 즉시로딩이므로 LAZY로 수동 설정해줘야 합니다. 

 

 

  본 포스팅은 자바 ORM 표준 JPA 프로그래밍을 참고하여 작성했습니다. 

'JPA' 카테고리의 다른 글

엔티티의 상태 - 준영속  (0) 2024.06.07
플러시란?  (0) 2024.06.07
영속성 컨텍스트의 특징  (0) 2024.06.07
엔티티의 생명주기  (0) 2024.06.07
상속 관계 매핑 - @MappedSuperclass  (0) 2024.06.06