반응형
JPA : N + 1 문제
원인
- 하나의 부모 엔터티를 조회 하면, 관련된 N개의 자식 엔터티를 개별적으로 조회 하면서 총 N+1 번의 쿼리가 실행
- JPA에서 Lazy Loading을 사용할 때 발생하는 문제
문제발생 예제
@Entity
@Getter @Setter
public class Notice {
@Id @GeneratedValue
private Long id;
private String title;
@OneToMany(mappedBy = "notice", fetch = FetchType.LAZY)
private List<NoticeFile> noticeFiles = new ArrayList<>();
}
@Entity
@Getter @Setter
public class NoticeFile {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "notice_id")
private Notice notice;
}
문제발생 예제 : 수행 코드
List<Notice> notices = em.createQuery("SELECT n FROM Notice n", Notice.class)
.getResultList();
for (Notice notice : notices) {
System.out.println("Notice: " + notice.getTitle());
for (NoticeFile noticeFile : notice.getNoticeFiles()) { // N+1 문제 발생 지점 (Lazy 호출하는 시점에 조회)
System.out.println("NoticeFile: " + noticeFile.getName());
}
}
문제발생 예제 : 수행 결과
SELECT * FROM Notice; -- (1) 부모 엔티티 조회 (1회 실행)
SELECT * FROM NoticeFile WHERE notice_id = ?; -- (N) 자식 엔티티 조회 (N번 실행)
SELECT * FROM NoticeFile WHERE notice_id = ?;
SELECT * FROM NoticeFile WHERE notice_id = ?;
...
최초 Notice를 조회 하였고, (조회 1번)
상세 NoticeFile에 접근 하려고 하니, Notice의 목록 수 만큼 NoticeFile를 조회 함. (조회 N번)
해결
* 1. fetch join 사용: @Query("Select p From Parent p JOIN FETCH p.children")
2. @EntityGraph 활용 : @EntityGraph(attributePaths="children")
3. batch_size 설정 : hibernate.default_batch_fetch_size=100 (application.properties)
해결 1. fetch join 사용 [권장]
- 쿼리 실행 횟수 1회로 줄어들어 성능 최적화
- 즉시 로딩(Eager Loading)과 비슷하지만, JPQL에서만 사용되므로 다른 쿼리에 영향 없음
- 다대일(N:1) 관계에서도 유용
List<Notice> notices = em.createQuery("SELECT n FROM Notice n JOIN FETCH n.noticeFiles", Notice.class)
.getResultList();
해결 1. fetch join 사용 : 수행 결과
SELECT n.*, f.* FROM Notice n
JOIN NoticeFile f ON n.id = f.notice_id; -- 단일 쿼리로 모든 데이터 조회
해결2. @EntityGraph 활용 (Spring Data JPA)
@EntityGraph를 사용하면 JPQL 없이도 Fetch Join 효과를 적용 가능
- JPQL 없이 Fetch Join 효과
- Repository 단에서 해결 가능
@Repository
public interface NoticeRepository extends JpaRepository<Notice, Long> {
@EntityGraph(attributePaths = "noticeFiles")
List<Notice> findAll();
}
해결2. @EntityGraph 활용 : 수행 결과
SELECT n.*, f.* FROM Notice n
JOIN NoticeFile f ON n.id = f.notice_id;
해결3. hibernate.default_batch_fetch_size 설정
여러 개의 Lazy Loading을 한 번에 가져오도록 Batch Fetching 적용
- 개별 쿼리 실행을 줄이고, 한번의 IN 쿼리로 조회
- Fetch Join을 사용할 수 없는 경우 대안이 될 수 있음
#properties
spring.jpa.properties.hibernate.default_batch_fetch_size=100
해결3. hibernate.default_batch_fetch_size : 수행 결과
SELECT * FROM Notice;
SELECT * FROM NoticeFile WHERE notice_id IN (?,?,? .... ); -- Batch 처리로 한 번에 조회
- default.batch_fetch_size=100 크기를 넘어서면 오류 발생
반응형
'skill > Etc' 카테고리의 다른 글
[php] 목록 제목 검색 글자 색 다르게 표기 (0) | 2024.06.04 |
---|---|
[javascript] 목록 검색 글자 색 다르게 표기 (0) | 2024.06.03 |
공동 작업 시, npm install, npm audit fix --force 오류 (0) | 2023.03.30 |
MAC .jmx 실행 및 요약보고서 Error 확인 방법 - jmeter (성능테스트) (0) | 2023.03.30 |
Gradle sync failed: No matching variant of org.springframework.boot:spring-boot-gradle-plugin:3.0.2 was found Error (0) | 2023.01.30 |