티스토리 뷰

1. JPA의 연관관계 로딩 전략(LAZY)

분류 설명
RDB 연관 관계를 가진 데이터 조회 시, join을 통해 필요 데이터를 한번에 로딩
객체 연관 관계를 필드로 가지고 있기 때문에,
연관된 객체가 많다면 불필요하게 많은 테이블이 조인됨

1.1 JPA에서의 사용

:: So, 위와 같은 문제 때문에 JPA에서는 LAZY(지연 로딩) 전략을 제공함

:: 연관 관계 객체를 즉시 가져오는 것이 아닌 사용할 때 가져옴 -> 성능 최적화를 이룰 수 있음

:: 연관 객체의 경우, 처음에는 Proxy 객체로 설정되어 실제로는 빈 깡통이지만 있긴 함

:: 기본은 LAZY로 설정 후, 필요한 경우에 Join Fetch 또는 EntityGraph를 사용해 EAGER처럼 가져오는 방식을 사용하기

구분 LAZY (지연 로딩) EAGER (즉시 로딩)
정의 연관 객체를 실제 사용 시점에 쿼리로 조회 연관 객체를 즉시(주 객체 조회 시) 함께 조회
동작 방식 연관 객체는 처음엔 프록시 객체로 대체,
실제 프로퍼티에 접근할 때 쿼리 발생
주 엔티티를 조회할 때 즉시 조인 쿼리로 함께 로딩
기본 값 @OneToMany || @ManyToMany @OneToOne || @ManyToOne
장점 성능 최적화 가능, 불필요한 쿼리 방지 편리함. 연관 객체를 바로 사용할 수 있음
단점 사용 시점에 쿼리 → N+1 문제 발생 가능성 연관된 객체가 많으면 불필요한 데이터 과다 로딩 발생
사용 시점 대부분의 상황 (실무 기본 전략) 연관 객체가 항상 필요하고, 개수도 작을 때만 사용

1. N + 1 문제

:: 1번의 쿼리로 부모 엔티티를 조회한 후, 부모 엔티티의 수만큼 추가 쿼리가 발생하여 자식 엔티티를 개별 조회하는 현상

:: => 1개의 쿼리에 N개의 추가 쿼리가 발생하는 현상

// Ex)
List<Team> teams = teamRepository.findAll(); // 쿼리 1번
for (Team team : teams) {
    System.out.println(team.getMembers()); // 팀 수만큼 쿼리 발생 (N번)
}

 

1.1 원인

종류 설명
JPA의 연관관계 기본 로딩 전략이 LAZY인 경우 → 실제 접근 시 쿼리가 날아감
→ 반복문 안에서 연관 객체 호출 시 반복적으로 쿼리 발생
EAGER의 경우에도 발생 가능 → findById의 경우에는 JOIN의 자동 최적화가 되어 상관없지만,
→ findByAll의 경우에는 JOIN이 발생하지 않아 N+1 발생
더보기
더보기
전략 단일 조회 (findById) 다수 조회 (findAll) N+1 문제
EAGER JOIN 으로 연관객체도 같이 조회됨 연관 객체는 따로 N번 쿼리 발생 O
LAZY 연관객체 접근 전까지는 쿼리 없음 객체 하나씩 접근 시마다 쿼리 발생 O
  • EAGER 전략을 사용하고, 처음에 쿼리해 가져올때 N+1 문제가 발생
  • LAZY 전략을 사용하더라도, 나중에 쿼리해 가져올때 N+1 문제가 여전히 발생
    ==> N+1 에서 N 을 미리하냐(EAGER) 나중에하냐(LAZY)의 차이

1.2 해결 방법

:: EAGER / LAZY 설정만으로는 해결이 불가능 함

  1. @fetch join :: 연관객체를 함께 가져오도록 쿼리 작성(JPQL 에서 제공)
  2. @EntityGraph :: 선언적으로 fetch join처럼 연관객체 미리 조회
  3. @BatchSize :: Lazy 조회 성능 최적화 – 여러건 한 번에 조회

를 통해 해결할 수 있음

 

1. @fetch join

@Query("SELECT t FROM Team t JOIN FETCH t.members")
List<Team> findAllWithMembers();
  • 한 번의 쿼리로 연관된 members까지 함께 조회 (N+1 방지)

1-1. @fetch join과 Pagination을 함께 쓸 경우?

@Query("SELECT u FROM User u JOIN FETCH u.messages")  
List<User> findAllUsers();  
// User와 연관된 messages를 한 번에 조회하기 때문에 N+1 문제는 해결
// But, Pagination (Pageable)과 함께 쓰면 문제가 생김

So, 1만개의 User가 있다고 가정할 때,

 

  • 원래 의도 :: 1페이지 10명만 가져와야 함
  • FETCH JOIN 쓴 경우
    :: DB는 User 기준 10개가 아닌 1만 개 전부 가져온 뒤,
    :: Java에서 List<User>로 변환하면서 중복 제거 및 페이지 나눔

 

:: 데이터는 JOIN 때문에 부풀고, JPA는 메모리에서 페이지 계산 → 성능 폭망

구분 설명
@OneToOne / @ManyToOne + FETCH JOIN
(단건 연관 관계)
- 페이징에 사용 가능,
- 객체에 연관 객체또한 1개이기에, 조인해도 row 늘어나지 않음
@OneToMany + FETCH JOIN - 페이징에 사용 XXXX
- 객체에 연관 객체가 많음 => row 폭증 → 페이징 왜곡 가능
@BatchSize - 추천하는 사용법 (배치 사이즈만큼 묶어서 쿼리)
- LAZY로 가져오되, SELECT IN 으로 최적화
   (Hibernate가 LAZY로 불러올 때, 일정 개수씩 묶어서 한번에 조회)
@Fetch(FetchMode.SUBSELECT) - 추천하는 사용법
- 다 가져온 뒤, 연관객체들을 한 번의 서브쿼리로 조회
   (Hibernate가 객체를 한번에 조회 후, 1번의 서브 쿼리로 모든 연관 객체를 조회)
   (N번의 쿼리가 2번으로 축소)
더보기
더보기

1. OneToOne / @ManyToOne + FETCH JOIN

@Entity
public class Message {
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;  // 단건 연관
}

2. @BatchSize

@Entity
public class User {
    @OneToMany(mappedBy = "user")
    @BatchSize(size = 100)
    private List<Message> messages;
}

를 통해 100개씩 묶어서 처리하는 예시

SELECT * FROM message WHERE user_id IN (?, ?, ..., ?) // SQL문 예시

3. @Fetch(FetchMode.SUBSELECT)  

@Entity
public class User {
    @OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
    @Fetch(FetchMode.SUBSELECT)
    private List<Message> messages;
}

를 통해 1번의 전체 부르기 + 1번의 서브 퀄;

SELECT * FROM user WHERE ...;

SELECT * FROM message 
WHERE user_id IN (
    SELECT id FROM user WHERE ...
) // SQL문 예시

  

 


2. @EntityGraph

:: 연관관계 필드를 즉시 로딩처럼 동작하게 해주는 방법

:: 하지만 실제 동작의 경우에는 EAGER가 아니라 JPQL에 JOIN FETCH를 자동으로 붙여주는 방식

@EntityGraph(attributePaths = "members")
List<Team> findAll();
  • 선언적 방식으로 fetch join과 유사한 효과(연관 엔티티를 자동으로 join feth해서 가져옴)
  • 코드 간결, 재사용 용이

 

3. @BatchSize

@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members;

 

  • 여러 건의 지연 로딩을 한 번에 처리 (IN 쿼리로 묶음)
  • 완벽한 해결은 아니지만 성능 향상 가능

참고

ASAC 수업자료

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함