정의
연관 관계가 설정된 엔티티를 조회할 때, 조회된 데이터 개수(N)만큼 연관 관계 조회 쿼리가 추가로 발생하는 현상
→ 즉, 한 번의 조회(1)로 다수의 엔티티(N)를 가져왔는데, 각 엔티티마다 연관된 데이터를 조회하기 위해 추가 쿼리 N개가 발생하는 현상
예시 상황
블로그 유저와 게시글이 1:N 관계일 때
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "user", fetch = FetchType.EAGER) // 즉시 로딩
private List<Post> posts = new ArrayList<>();
}
@Entity
public class Post {
@Id @GeneratedValue
private Long id;
private String title;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
findAll()로 모든 유저(User)를 조회하면, 각 게시글(Post)을 조회하기 위한 추가 쿼리 발생
findAll() 글로벌 패치 전략별 N + 1 문제 상황
findAll() 동작 시 쿼리 흐름
- 내부적으로 실행되는 JPQL:
select u from User u; - 글로벌 패치 전략을 고려하지 않고 쿼리 실행해 단순히
User만 조회
글로벌 패치 전략
| FetchType | 쿼리 동작 | N+1 발생 여부 | 설명 |
| EAGER (즉시로딩) | 각 User마다 연관 엔티티(Post)를 즉시 로딩 | ✅ 발생 | findAll()이 모든 User를 가져온 후, 각 User마다 Post를 즉시 조회 |
| LAZY (지연로딩) | 연관엔티티를 프록시로 로딩하고, 실제 접근 시 쿼리 실행 | ⚠️ 조건부 발생 | findAll() 시에는 N+1 없음, user.getPosts() 접근 시 N번 추가 쿼리 발생 |
즉시 로딩일 때 실행되는 SQL
-- findAll() 실행 시
select u from User u;
-- 이후 각 user의 posts를 즉시로딩
select p from Post p where p.user_id = ?;
select p from Post p where p.user_id = ?;
select p from Post p where p.user_id = ?;
...
→ User 1번 조회해도, 연관된 POST N번 조회 ⇒ 총 N + 1 쿼리 발생
N + 1 문제 해결 방법
1. Fetch Join 사용
JPQL 에서 join fetch 구문을 사용하여 한 번의 쿼리로 연관 엔티티까지 함께 로딩
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select distinct u from User u left join fetch u.posts")
List<User> findAllWithPosts();
}
- 실행되는 SQL 예시:
select distinct u.*, p.* from user u left join post p on u.id = p.user_id; - → 한 번의 쿼리로 User + Post 모두 조회
2. @EntityGraph 사용
fetch join과 동일한 효과를 어노테이션 기반으로 제공
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph(attributePaths = {"posts"}, type = EntityGraphType.FETCH)
List<User> findAll();
}
→ 내부적으로 left join fetch u.posts가 자동 추가되어 실행
→ 코드가 더 깔끔하고, 재사용성 높음
3. Batch Size 조정 (Hibernate 전용)
@BatchSize나 글로벌 설정으로 일정개수의 연관 엔티티 묶어서 조회하도록 설정
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
@BatchSize(size = 100)
private List<Post> posts;
# application.yml
spring:
jpa:
properties:
hibernate.default_batch_fetch_size: 100
→ N + 1 → N/100 + 1 로 성능 개선 가능 (N번의 쿼리를 100개단위로 묶어서 실행)
요약
| 해결 방법 | 설명 | 장점 | 단점 |
| Fetch Join | JPQL로 한 번에 연관 엔티티 로딩 | 완전한 해결 | 복잡한 쿼리 작성 필요 |
@EntityGraph |
어노테이션 기반 fetch join | 간결, 재사용성 높음 | 복잡한 조건 쿼리엔 제약 |
| Batch Size 조정 | 여러 연관 엔티티를 묶어서 조회 | 간단히 적용 가능 | 완전한 해결은 아님 |
'Spring' 카테고리의 다른 글
| EntityManager (엔티티 매니저) (0) | 2025.10.22 |
|---|---|
| JPA ddl-auto 옵션 (0) | 2025.10.21 |
| Spring Data JPA에서 새로운 Entity인지 판단하는 방법 (0) | 2025.10.20 |
| [Spring] 빈 스코프(Bean Scope) (0) | 2024.10.01 |
| [Spring] 빈 생명주기 콜백(@PostConstruct, @PreDestroy) (0) | 2024.09.26 |





