JPA - 즉시로딩과 지연로딩
- Team(1), Member(N) 관계에 테이블이 있다.
- Member 를 조회할때, 꼭 Team 정보를 같이 가져와야 할까?
- 매번 Member 를 조회할때마다 Team을 가져오는것은 불필요하고 낭비
- Team 데이터를 사용할때만 가져오게 한다.
- 내부적으로 Lazy 로딩방식을 사용하게되고, 이때 Team객체는 프록시객체로 가져오게 된다.
- (참고로 Django는 default 값이 Lazy 로딩방식이다.)
01. 지연로딩 (Lazy Loading)
Entity
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.LAZY) // Lazy 로딩
@JoinColumn(name = "TEAM_ID")
private Team team;
....
}
Logic
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member1 = new Member();
member1.setName("member1");
member1.setTeam(team);
em.persist(member1);
em.flush(); // 영속성 컨텍스트에 있는 쿼리문들을 실제 DB로 날린다.
em.clear(); // 영속성 컨텍스트를 비운다.
Member m = em.find(Member.class, member1.getId());
System.out.println("m = " + m.getTeam().getClass());
team.getName(); // 실제 team 데이터를 사용할때 데이터를 가져오게 된다.
tx.commit()
02. 즉시로딩 (Eager Loading)
- Member를 조회할 때, Team도 같이 데이터를 가져온다. (join)
Entity
@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;
....
}
03. 프록시와 즉시로딩 주의
- 가급적 지연 로딩만 사용 (실무에서)
- 즉시 로딩을 적용하면 예상치 못한 SQL이 발생
- 즉시 로딩은 JPQL 에서 N+1 문제를 일으킨다.
- @ManyToOne, @OneToOne은 기본이 즉시 로딩이므로, 지연로딩으로 설정하자!
- @OneToMany, @ManyToMany 는 기본이 지연 로딩
04. 주의점
- Django 와 Spring 에서의 N+1 문제가 서로 다른 케이스에 발생하는 문제
- Django
- Lazy Loading 에서 N+1 문제 발생
select_related
,prefetch_related
로 해결 가능하다. (정참조, 역참조)
- Spring
- Eager Loading 에서 N+1 문제 발생 (JPQL 사용시?)
- Lazy Loading 으로 설정하고 필요한 데이터는
fetch join
으로 가져오면 해결 가능하다.
ChatGPT
N+1 문제는 Lazy Loading 방식일 때 발생하는 문제입니다.
Lazy Loading 방식에서는 필요한 시점에 데이터를 가져오는 것이 원칙이기 때문에, 데이터를 사용할 때마다 쿼리를 실행하여 데이터를 가져옵니다. 이 때, 관련된 객체를 함께 가져와야 할 때, 관련된 객체들의 데이터를 가져오는 추가적인 쿼리가 발생할 수 있습니다. 이 때, 쿼리가 N+1번 실행되어 성능 저하가 발생하는데, 이를 N+1 문제라고 합니다.
반면, Eager Loading 방식에서는 필요한 데이터를 한 번에 모두 가져오기 때문에, 추가적인 쿼리가 발생하지 않습니다. 따라서, Eager Loading 방식에서는 N+1 문제가 발생하지 않습니다.
N+1 문제는 기본적으로 지연로딩일 때 발생한다고 하지만, Spring에서 제공해주는 JPQL 에서는 즉시로딩에서 나타나게 되는 문제가 있다.
이는 제가 Django에서 알고있던 개념과 살짝 달라서 혼동이 오는 부분입니다.
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();
List<Member> members = em.createQuery("select m from Member m ", Member.class)
.getResultList() // 이 코드에서 JPQL 이 sql문으로 변환되면서 EAGER 로 가져와야하기 때문에 쿼리가 한 번 더 호출되는 문제가 발생한다.
05. 정리
지연 로딩 활용 - 실무
- 모든 연관관계에 지연로딩 사용할 것
- 실무에서 즉시 로딩 사용 X
- JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해라.
- 즉시 로딩은 상상하지 못한 쿼리가 나간다.
댓글남기기