2025-11-07
오늘 배운 것
📌 정의
하나의 쿼리를 실행했는데, 연관된 데이터를 불러오느라 추가 쿼리가 N번 발생하는 문제
즉,
1번의 조회 쿼리 + N번의 추가 쿼리 → 총 N+1 쿼리 발생!
🎯 예시 상황
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members;
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Team team;
}
🧨 문제가 되는 코드
java
코드 복사
List<Team> teams = teamRepository.findAll(); // ✅ 쿼리 1번
for (Team team : teams) {
System.out.println(team.getMembers()); // ❗ 매 팀마다 쿼리 N번
}
👉 실행 쿼리 요약
sql
코드 복사
SELECT * FROM team; -- 1번
SELECT * FROM member WHERE team_id = 1; -- N번 반복
SELECT * FROM member WHERE team_id = 2;
...
✅ 예상: 1번 쿼리
❌ 실제: N+1 쿼리 → 성능 저하
📦 발생 원인
| 원인 | 설명 |
|---|---|
| 연관관계의 기본 fetch 전략 | @OneToMany, @ManyToOne → 기본이 LAZY |
| 객체를 순회하며 getter 호출 | 그 시점에 실제 쿼리 날림 (프록시 초기화) |
✅ 해결 방법 1: Fetch Join
JPQL로 연관된 엔티티를 한 번에 JOIN해서 가져오기
@Query("SELECT t FROM Team t JOIN FETCH t.members")
List<Team> findAllWithMembers();
✅ 실행 쿼리:
SELECT t.*, m.* FROM team t
JOIN member m ON t.id = m.team_id
→ Team과 Member를 한 번에 조회!
✅ 해결 방법 2: @EntityGraph
JPQL 없이 fetch join처럼 작동
@EntityGraph(attributePaths = "members")
List<Team> findAll();
✅ 해결 방법 3: BatchSize (for LAZY 컬렉션)
JPA가 IN 쿼리로 모아서 한번에 가져오도록 설정
@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members;
또는 application.yml:
spring:
jpa:
properties:
hibernate.default_batch_fetch_size: 100