250x250
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 비관적락
- 유니크제약조건
- 동적sql
- 낙관적락
- 이진탐색
- JPQL
- exclusive lock
- FetchType
- CHECK OPTION
- SQL프로그래밍
- 연관관계
- fetch
- 즉시로딩
- 연결리스트
- eager
- querydsl
- execute
- 데코레이터
- shared lock
- PS
- 다대일
- 일대다
- dfs
- 힙
- 백트래킹
- BOJ
- 스토어드 프로시저
- 지연로딩
- 다대다
- 스프링 폼
Archives
- Today
- Total
흰 스타렉스에서 내가 내리지
[JPA] N+1 문제를 피할 수 있는 다양한 방법 본문
728x90
1. 페치 조인 사용
- 가장 일반적인 방법
select m from Member m join fetch m.orders
SELECT M.*, O.* FROM MEMBER M
INNER JOIN ORDERS O ON M.ID=O.MEMBER_ID
- 일대다 조인을 했으므로 중복된 결과가 나타날 수 있다.
- 따라서 JPQL 의 DISTINCT 를 사용해서 중복을 제거하는 것이 좋다.
2. 하이버네이트 @BatchSize
- 하이버네이트가 제공하는 org.hibernate.annotations.BatchSize 어노테이션을 사용하면 연관된 엔티티를 조회할 때 지정한 size 만큼 SQL 의 IN 절을 사용해서 추가 조회한다.
- 만약 조회한 회원이 10명인데 size=5 로 지정하면 2번의 SQL 만 추가로 실행한다.
@Entity
public class Member{
...
@org.hibernate.annotations.BatchSize(size = 5)
@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
private List<Order> orders = new ArrayList<>();
...
}
SELECT * FROM ORDERS
WHERE MEMBER_ID IN (
?, ?, ?, ?, ?
)
- 즉시로딩으로 설정하면 조회시점에 10건의 데이터를 모두 조회해야 하므로 SQL 이 2번 실행된다.
- 지연 로딩으로 설정하면 지연로딩된 엔티티를 최초 사용하는 시점에 다음 SQL 을 실행해서 5건의 데이터를 미리 로딩해둔다.
- 그리고 6번째 데이터를 사용하면 추가로 SQL 을 실행한다.
application.yml 설정파일에
hibernate.default_batch_fetch_size 속성을 사용하면 애플리케이션 전체에 기본으로 @BatchSize 를 적용할 수 있다
3. @Fetch(FetchMode.SUBSELECT)
- 하이버네이트가 제공하는 org.hibernate.annotations.Fetch 어노테이션에 FetchMode 를 SUBSELECT 로 사용하면 연관된 데이터를 조회할 때 서브 쿼리를 사용해서 N+1 문제를 해결한다.
@Entity
public class Member{
...
@org.hibernate.annotations.Fetch(FetchMode.SUBSELECT)
@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
private List<Order> orders = new ArrayList<>();
...
}
- 다음 JPQL 로 회원 식별자 값이 10을 초과하는 회원을 모두 조회해보자.
select m from Member m where m.id > 10
- 즉시로딩으로 설정하면 조회 시점에, 지연 로딩으로 설정하면 지연 로딩된 엔티티를 사용하는 시점에 다음 SQL 이 실행된다.
SELECT O FROM ORDERS O
WHERE O.MEMBER_ID IN (
SELECT M.ID
FROM MEMBER M
WHERE M>ID > 10
)
# 정리
- 즉시 로딩은 사용하지 말고 지연 로딩을 사용하는 것을 권장한다.
- 즉시 로딩의 가장 큰 문제는 성능 최적화가 어렵다는 점이다.
- 엔티티를 조회하다보면 즉시 로딩이 연속으로 발생해서 전혀 예상하지 못한 SQL 이 실행될 수 있다.
- 따라서 모두 지연로딩으로 설정하고 성능 최적화가 꼭 필요한 곳에는 JPQL 페치 조인을 사용하자.
- 기본값이 즉시로딩인 @OneToOne 과 @ManyToOne 은 fetch = FetchType.LAZY 로 설정해서 지연로딩 전략을 사용하도록 변경하자.
'JPA' 카테고리의 다른 글
[JPA] 배치 처리 (1) | 2024.04.28 |
---|---|
읽기 전용 쿼리의 성능 최적화 - @Transactional(readonly=true), setHint(readonly) (1) | 2024.04.28 |
프록시로 조회해도 영속성 컨텍스트는 영속 엔티티의 동일성을 보장한다. (0) | 2024.04.28 |
테스트 환경에서 엔티티 비교 (0) | 2024.04.28 |
[Spring-data-JPA] 벌크성 수정 쿼리 (0) | 2024.04.25 |