JPA N+1
간단하게 이해하기 쉽게 설명하자면, 엔티티를 조회했는데 쿼리가 한번이 아닌 + N번 더 실행하여 성능을 저하시키는 문제를 말한다.
상황
1.
ex) select * from MainEntity where name = '?'
이러한 쿼리를 통해서 MainEntity를 N개 가져왔을 경우 + MainEntity와 연관관계를 맺은 Entity가 ToOne관계일때 (지연로딩이던 즉시로딩이건) 연관관계를 맺은 Entity를 가져올때 N번의 쿼리가 더 발생하게 된다.
코드예시
public class MainEntity{
@ManyToOne
private 연관Entity;
}
public findMainEntity(){
//Main Entity를 가져온다. -> query 1번
List<MainEntity> results = findByMainEntityByWhere(String name);
//for문을 돌면서 ToOne연관관계를 가진 Entity를 가져온다.
for(MainEntity result: results ){
//쿼리 한번
result.get연관Entity();
}
//즉 반복문 수 만큼 query가 나간다.
}
여기서 가장 큰 문제는 ToOne관계를 가진 Entity가 여러개라면 그냥 1+N+N+N ----- 이렇게 엉청난 성능 이슈를 가져올 수 있다.
해결법.
Fetch Join으로 모두 join을 통해서 한번에 가져오게 되면, 성능이슈 없이 한번에 가져올 수 있다.
fetch join이 번거롭다면
@EntityGraph 를 사용한다면 명시만해도 연관된 모든 Entity를 가져와준다.
2. 위와 같은 상황이지만 ToMany 인 연관관계가 있을 때.
똑같이 fetch join으로 가져오거나 @EntityGraph를 이용하여 불러오면 된다.
public class MainEntity{
@OneToMany
private List<연관Entity>;
}
*하지만 컬렉션(즉 ToMany) 관계일 때 모두 fetch join으로 불러오면 페이징 처리가 불가능하다.
이유는?
query문으로 보았을 때
1대 다 연관관계를 join하게 되면 MainEntity가 한개더라도 join되는 수에따라 결과값이 늘어나게된다.
select * from MainEntity inner join 연관Entity on MainEntity.id = 연관Entity.id where MainEntity.id =1 ;
이 쿼리를 보면 MainEntity의 결과값이 하나였더라도, join되는 컬럼의 수만큼 결과값이나온다.
그렇다면 위와 같은 이유로 (데이터가 3개라고 가정하면) MainEntity가 3개라고 인지한 JPA는 그 결과값만큼 중복된 데이터를 생산하게 되고,
이를 해결하기 위한 방법은 jpql에 Distinct 를 같이 써주는것이다. 하지만 이것으로는 페이징 처리를 할 수 없다.
해결법.
ToOne관계들만 모두 Fetch Join으로 끌고오고, ToMany는 @BatchSize로 가져오게되면, IN연산자를 사용하여 한번에 끌고오게된다. 즉 ToOne을 Paging 처리하여 원하는 수만큼 가져오고, 그에 맞게 IN연산자를 통해서 ToMany엔티티를 불러오는데 그것이 @BatchSize이다.
DTO 조회방식.
이 방식은 Fetch Join을 사용할 수 없다.
왜? result를 DTO로 받는 방식이기 때문이다.
ex) 위와 동일한 방식으로 ToOne관계는 Join으로 모두 받아온다.
em.createQuery("select new DTO() from Entity where ~~")
이 방식으로 우선 ToOne관계를 가져오고.
아래는 수도코드이다.
var result = findDtoById();
var ids = result.stream().collect(getId~~);
em.createQuery("select new dto from ~~ where id in {ids}")
.setParameters(ids);
Map<Long,Dto> = Map(groupBy(getID));
result.stream().setMany(map);
아래와 같이 ToMany관계는 ids를 뽑아내서 in절로 가져온후 id와 매핑시켜줘서 기존꺼에 넣는 방식이다.
*앤타타 조회 방식은 JPA가 많은 부분을 최적화 해주기 떄문에, 단순한 코드를 유지하면서, 성능을 최적화 할 수 있다.
반면에 DTO 조회 방식은 SQL을 직접 다루는 것과 유사하기 때문에, 둘 사이에 줄타기를 해야한다.*
'Trouble Shooting' 카테고리의 다른 글
| 태태개발일지 - SpringBoot에서 JSP사용하는 방법 (0) | 2025.10.08 |
|---|---|
| 태태개발일지 - 네이버 API 등록 (0) | 2025.07.09 |
| 태태개발일지 - GPT 파인 튜닝 (0) | 2025.06.23 |
| 태태개발일지 - JpA,Mybatis,다중DB 한번에 사용? (1) | 2024.10.28 |