즉시로딩과 지연로딩

 

 

즉시로딩과 지연로딩은 JPA에서 엔티티 간의 연관관계에서 데이터를 가져오는 방식에 대한 설정이다.

 

1. 즉시 로딩 (Eager Loading)

  • 특징: 엔티티가 조회될 때, 연관된 모든 엔티티도 즉시 함께 로드됩니다.
  • 장점: 관련된 데이터를 한 번에 가져오기 때문에, 필요한 데이터가 즉시 사용 가능합니다.
  • 단점: 모든 연관 데이터를 즉시 로딩하므로, 불필요한 데이터까지 로딩될 수 있어 성능에 부담이 될 수 있습니다.
  • 사용 방법: @OneToOne(fetch = FetchType.EAGER) 또는 @ManyToOne(fetch = FetchType.EAGER)로 설정합니다.

2. 지연 로딩 (Lazy Loading)

  • 특징: 엔티티가 처음 조회될 때는 연관된 데이터를 가져오지 않고, 실제로 사용할 때 필요한 시점에 데이터를 로드합니다.
  • 장점: 처음부터 모든 데이터를 로드하지 않으므로, 성능을 최적화할 수 있습니다.
  • 단점: 연관 데이터에 접근할 때 추가적인 쿼리가 실행되므로, 트랜잭션이 닫힌 상태에서는 LazyInitializationException이 발생할 수 있습니다.
  • 사용 방법: @OneToMany(fetch = FetchType.LAZY) 또는 @ManyToMany(fetch = FetchType.LAZY)로 설정합니다.

 

@Entity
@Data
public class Member{

	@Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    @Column(name = "USERNAME")
    private String username;
    
    
    //team과의 연관관계 설정
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    
    
}

@Entity
@Data
public class Team{

	@Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    private String name;
    
    
}

 

 

지연 로딩 (Lazy Loading)

@ManyToOne(fetch = FetchType.LAZY) 를통해 연관관계에 있는 team 객체를 조회할때 member클래스만 DB에서 조회하고 team객체는 프록시 객체로 조회해온다.(실제값이 없는 가짜 객체상태)

 

**프록시 객체:

●프록시 객체는 실제 데이터베이스 조회를 지연시키기 위해 사용되며, 처음에는 데이터가 조회되지 않고 필요할 때 실제 데이터를 불러옵니다.

● 프록시 객체는 엔티티의 메서드를 호출하는 시점에 데이터베이스에서 데이터를 가져오도록 설계되어 있습니다.

 

Member m = em.find(Member.class, member.getId()); 를 통해 DB에서 조회하면 team객체는 값이 할당되지 않은 프록시 객체로 남아있다.

하지만 m.getTeam().getName() 을 통해 실제로 team객체에 대한 사용을 하는 시점에 프록시 객체가 초기화 되어 필요한 순간에 DB에서 값을 가져온다 (성능 최적화)

m.getTeam()만을 해서는 실제 값을 가져오지는 않는다. 이것은 단지 프록시객체를 가져오는 것이기 때문이다.

 

*멤버와 팀을 거의 같이 사용하지 않고 가끔씩만 연관해서 조회해야한다면 지연 로딩이 성능에 이점을 가져올 수 있다.

 

즉시 로딩으로 설정한다면 즉시 값을 DB에서 가져와서 프록시 객체가 아닌 실제값이 담기게 된다. 

(Member조회시 항상 Team도 같이 조회해온다.)

 

 

 

즉시로딩의 주의점

사용하지도 않는 객체의 데이터를 가져와서 데이터 병목현상이 있을 수 있고 성능 최적화가 되지 않을 수 있다.

 

JPQL 문법으로 조회 사용시:

List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();

이렇게 조회해올때 select 쿼리가 2번이 나간다. 연관된 엔티티객체가 있다면 일일이 조회 쿼리가 나간다.

(즉시로딩 설정일때) --> N+1 문제, 최초쿼리 한번에 N(쿼리결과)번에 쿼리가 실행된다.

em.find는 join 쿼리로 최적화 되어서 조회해온다.

 

 

예를 들어, **회원 엔티티(User)**와 **주문 엔티티(Order)**가 @OneToMany 관계로 연결되어 있고, User 엔티티의 Order가 즉시 로딩으로 설정되어 있다고 가정합니다.

  • 쿼리 1개로 User 10명을 조회하면, JPA는 각 User마다 즉시 로딩 설정에 따라 연관된 Order를 가져옵니다.
  • 만약 한번의 조회쿼리로 User의 조회 쿼리결과가 10개라면, 관련 엔티티인 Order의 쿼리가 10번 나간다.
  • 결과적으로 총 1개의 User 조회 쿼리 + 10개의 Order 조회 쿼리가 실행되어 총 11개의 쿼리가 발생합니다.

이러한 문제의 원인은 무엇인가?

 

N+1 문제의 원인

JPA는 JPQL을 사용할때 글로벌 페치 전략을 무시하고 JPQL 문법만 사용하기때문에 

 

그렇다면 즉시로딩일때만 이 문제가 발생하는가?  아니다 지연로딩일때도 발생한다.

FetchType의 변경은 단지 N+1문제에 발생시점의 차이일 뿐이다.

 

즉시로딩일 경우: JPQL을 통한 결과 조회를 해옴 --> JPA가 내부적으로 즉시로딩을 해온다, 결국 하위 엔티티들을 추가로 조회해온다 -->  N+1의 문제 발생

지연로딩 : 지연로딩이기 때문에 프록시 객체로 조회해온다. 하지만 연관된 하위 엔티티에 접근하는 순간 추가 조회가 일어나기 때문에 결국 N+1문제가 발생.

 

그렇다면 해결방법은? 

 

 

N+1 해결방안

1.fetch조인    2. @EntityGraph

 

1. fetch조인 : 부모 엔터티와 연관된 자식 엔터티를 한 번의 쿼리로 같이 가져오는 방식

 

ex: select m from Member m join fetch m.team;

 

Member와 연관된 Team 엔터티를 한 번에 가져온다.

따라서 Member 엔터티를 가져올 때 Team 엔터티가 필요한 경우에도 추가 쿼리가 발생하지 않아 성능을 높일 수 있다.

 

이미 Team을 조인해서 가져왔기 때문에 Team에 대한 추가 쿼리가 나가지 않는다.

(Team에 프록시 객체가 아닌 실제 값이 담긴다.)

 

fetch가 붙으면 뭐가 달라지는가?  -->

일반 조인이면 연관된 엔티티를 함께 조회하지 않는다.

그냥 select절에서 지정한 엔티티만 조회한다.

fetch조인은 즉시 로딩이 된다고 생각하면 된다.

 

 

2. @EntityGraph

.JPQL에서 명시적으로 적어줬던 fetch를 어노테이션을 통해 페치 조인을 구현한 것이다.

 

ex: 

@Override

@EntityGraph(attributePaths = {"team"})

List<Member> findAll();

 

하지만 내부적으로는 결론적으론 fetch join을 사용하는 것이다. 

일반 JPQL문에도 붙혀서 사용가능하고 네이밍 메서드에도 그냥 붙혀서 사용이 가능하다.

 

 

 

 

❗️@ManyToOne, @OneToOne은 기본이 즉시 로딩으로 설정되어있다.필요에 따라 수정하자. 나머지는 기본이 지연로딩.

 

 

 

'JPA' 카테고리의 다른 글

[JAVA] JPA_상속관계 매핑  (0) 2024.10.27
[JAVA] JPA_연관관계  (0) 2024.10.27
[JAVA] JPA_연관관계 매핑  (0) 2024.10.27
[JAVA] JPA_영속성 관리  (0) 2024.10.18
[JAVA] JPA란?  (0) 2024.10.18

+ Recent posts