Spring Data JPA : Eager, Lazy
@OneToOne(cascade, orphanRemoval, fetch = FetchType.LAZY/EAGER)
- JPA에서 로딩 전목이 동작하는 방식, 주의점
- 로딩 전목?
- 로딩 전목의 선택 기준, 사례
- Hibernate에서 N+1문제와 로딩 전목의 연관성, 해결법
JAP의 로딩 전략 : EAGER, LAZY
JPA에서 연관관계를 가지는 엔티티를 조회할 때, 해당 연관 엔티티를 어떻게 조회할 것인지 결정하는 전략
EAGR(즉시 로딩) : 엔티티를 조회할 때 연관된 엔티티를 함께 즉 - 시 조회함
LAZY(지연 로딩) : 엔티티를 조회할 때 바로 조회 X, 연관된 엔티티는 실제 사용하는 시점에서 쿼리를 내렸을 때 조회함
* 실무에서는 LAZY를 기본으로 사용하고, 필요하다면 fetch join이나 EntryGraph 등을 활용하여 조절한다고 함
EAGER : 즉시 동작 / 연관 엔티티를 JOIN을 통해 즉시 조회 / 사용이 편리하나 성능을 예측할 수 없음(N+1 위험)
LAZY : 필요할 때 동작 / 실제로 사용할 때 동작함(Proxy 객체) / 성능 제어에 유리하나 처음에는 Null값처럼 동작함
EAGER(즉시 로딩)
즉~시 로딩은 부모 엔티티를 조회할 때 연관된 자식 엔티티를 즉시 조회함
이 과정에서 join이 자동적으로 수행되며, Hibernate는 결과를 하나의 쿼리로 가져옴
public class User {
//
@OneToOne(fetch = FetchType.Eager) // 즉시 로딩
private UserStatus userStatus;
//
}
User를 조회하면 조회 시점에서 UserStatus도 함께 조회된다.
SELECT u.*, us.*
FROM users AS u
LEFT JOIN user_statuses AS us ON u.profile_id = us.id;
WHERE u.id = 124354646;
User를 조회하는 시점에서 쪼인이 이루어진다고 함
* join으로 인해 복잡한 객체 그래프를 한번에 가져옴
* 예상치 못한 쿼리 수행으로 성능이 저하될 수 있음
* N+1의 문제 발생 가능성이 높다고 합니다.
LAZY가 아닌 EAGER을 사용해도 N+1이 왜 발생하는가?
EAGER는 무조건 JOIN해라가 아니다.
조상 클래스를 조회할 때 자손 클래스를 '즉시' 가져오라는 의미임
JPA는 상황에 따라 JOIN을 하지 않을 수 있으며, 별도의 select 쿼리를 사용할 수 있다..
컬렉션 관계 특성상, JOIN을 할 수 없음
- > @ManyToOne, @OneToOne 관계는 EAGER을 사용하여 즉시로딩하면 JOIN이 자주 사용되지만
@OneToMany는 JOIN을 할 수 없다. 이건 SQL의 구조상 불가능하다고 함
따라서 EAGER이라고 해도 연관된 컬렉션은 N번 조회할 수 밖에 없어서 N+1 문제가 발생해버림
ORM 구현체의 한계?
Hibernate는 즉시로딩일 때도 연관객체가 필요한 시점에 별도의 쿼리를 실행할 수 있다..
EAGER는 영속성 컨텍스트 진입 시점에서 무조건 데이터를 가져오려는 의도를 가지지만, 방법은 N번의 SELECT일 수 있다
따 라 서
LAZY, EAGER 둘 다 N+1 문제를 해결할 수 없다..
LAZY 전략은 사용 시점마다 SELECT를 하여 N+1 문제가 발생할 수 있음 / fetch join으로 문제해결 가능
EAGER 전략은 처음 조회를 할 때 SELECT를 N번 할 수 있음 / fetch join, EntityGraph로 문제를 해결할 수 있음
* fetch join을 통해서 '어떻게 가져오냐'를 명시해야한다.
※
단건 관계를 조회하는 상황에선 LAZY + 필요할 때 fetch join을 씀
컬렉션 관계를 조회할때도 LAZY + 필요할 때 fetch join을 씀
복잡한 조회를 할땐 EntityGraph나 JPQL fetch join을 끌어와서 쓴다
LAZY(지연 로딩)
조회 시점에서 사용되는게 아니라, 내가 실제로 사용할 때 쿼리가 실행됨
최초 조회시에는 프록시(proxy) 객체만 주입됨
@Entity
public class Member {
//
@ManyToOne(fetch = FetchType.LAZY)
private Team team;
//
}
// team 객체를 사용하지 않아서 SELECT가 발생하지 않음, LAZY니까
Member member = memberRepository.findById(1L).get();
// team을 사용하는 순간 SELECT가 발생한다
Team team = member.getTeam();
// SQL문
select * from member where id = 1;
// team을 엑세스하면
select * from team where id = ?;
LAZY, 지연로딩은 필요할때만 조회하기 때문에 성능면에서 유리하다
초기에는 Proxy 객체이다(내부적으로 Hibernate 기술을 사용한다고 함)
* @ManyToOne, @OneToOne 관계에서는 LAZY 사용을 권장한다.
* @OneToMany에서는 무조건 LAZY를 사용해야한다. EAGLE 사용이 SQL 구조상 불가능하기 때문
* 복잡한 조회 최적화를 할 땐 fetch join을 통해 필요한 시점에 join을 직접 지정할 수 있다
* @EntityGraph를 활용할 수 있음
| EAGER, 즉시로딩 | LAZY, 지연로딩 | |
| 동작하는 시점 | 엔티티를 조회하는 즉시 | 자손이 필요할 때 조회함 |
| 언제 사용해야함? | 단순한 관계, 성능이 상관없을때 | 대부분의 실무에서 |
| N+1위험 | 높음 | 낮음(발생시 fetch join으로 해소가능) |
'Spring Boot' 카테고리의 다른 글
| Spring 안정성 높이기 : 애플리케이션 안정성 (3) | 2025.08.11 |
|---|---|
| Repository에서 @Query, QueryDSL 작성에 대해 (2) | 2025.08.02 |
| Spring Data JPA : 영속성 전이(Cascade) (0) | 2025.07.25 |
| Spring Data JPA : Repository 작성 (0) | 2025.07.25 |
| Entity : 연관관계 맵핑 (3) | 2025.07.25 |