public List<Order> findAllWithItem() {
return em.createQuery(
// JPA에서만의 기능으로 전체 row를 보는 것이 아닌 Order 엔티티만 봐서 중복 제거해서 보여준다.
"select distinct o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d" +
" join fetch o.orderItems oi" +
" join fetch oi.item i", Order.class
).getResultList();
}
컬렉션 페치 조인을 사용하면 페이징이 불가능하다.
컬렉션 페치 조인은 1개만 사용할 수 있다.
컬렉션 조회 시 N + 1 문제 발생 해결 방법
1. 제약이 있는 fetch join 하던지 - 단점 : 페이지 기능 못씀
2. toOne 관계는 fetch join 잡고, default_batch_fetch_size 기능 쓰던지 : 페이징 기능 사용 가능
default_batch_fetch_size: 100 값 쓰면 기존처럼 1개씩 select 쿼리 날리는게 아니라, 아래 SQL문에서 볼 수 있듯이, SQL in 구문안에 조회할 조건 값을 100개 넣어서 SQL을 한 번만 실행할 수 있도록 설정할 수 있다.
2번 동작 적용 순서
2-1) ToOne (OneToOne, ManyToOneO) 관계를 모두 fetch join 한다. ToOne 관계는 row수를 증가시지키 않기에 페이징 쿼리에 영향을 주지 않는다.
2-2) 컬렉션은 지연 로딩으로 조회한다.
2-3) 지연 로딩 성능 최적화를 위하여 default_batch_fetch_size 나 @BatchSize 적용한다.
해당 옵션을 설정하면 컬렉션이나 프록시 객체를 한꺼번에 설정한 size 만큼 IN 쿼리로 조회한다.
3. 쌩으로 default_batch_fetch_size 기능만 사용하는 경우도 있음.
public List<Order> findAllWithMemberDelivery(int offset, int limit) {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m " +
" join fetch o.delivery d", Order.class
).setFirstResult(offset)
.setMaxResults(limit)
.getResultList();
}
jpa:
hibernate:
ddl-auto: create # create, none. none으로 바꾸면 table 드랍하지 않음(data base에 넣어놓은 data 그대로 쓸 수 있음)
properties:
hibernate:
# show-sql: true
format_sql: true
default_batch_fetch_size: 100
2022-08-28 21:41:59.222 DEBUG 34760 --- [nio-9090-exec-1] org.hibernate.SQL :
select
item0_.item_id as item_id2_3_0_,
item0_.name as name3_3_0_,
item0_.price as price4_3_0_,
item0_.stock_quantity as stock_qu5_3_0_,
item0_.artist as artist6_3_0_,
item0_.etc as etc7_3_0_,
item0_.author as author8_3_0_,
item0_.isbn as isbn9_3_0_,
item0_.actor as actor10_3_0_,
item0_.director as directo11_3_0_,
item0_.dtype as dtype1_3_0_
from
item item0_
where
item0_.item_id in (
?, ?
)
private List<OrderItemQueryDto> findOrderItems(Long orderId) {
return em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)" +
" from OrderItem oi" +
" join oi.item i" +
" where oi.order.id = :orderId", OrderItemQueryDto.class)
.setParameter("orderId", orderId)
.getResultList();
}
// 앞의 버전에서는 모든 row를 대상으로 select query를 호출하였었는데, 이 방법에서는
// jpql where문 조건에 in을 활용해서 한 번에 가져온 다음
// 메모리에 Map으로 가져온 다음 메모리에서 매칭을 해서 값을 세팅해준다.
아래처럼 최적화해서 호출하면 query가 2번만 나간다.
public List<OrderQueryDto> findAllByDto_optimization() {
List<OrderQueryDto> result = findOrders();
List<Long> orderIds = result.stream()
.map(o -> o.getOrderId())
.collect(Collectors.toList());
List<OrderItemQueryDto> orderItems = em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)" +
" from OrderItem oi" +
" join oi.item i" +
" where oi.order.id in :orderIds", OrderItemQueryDto.class)
.setParameter("orderIds", orderIds)
.getResultList();
// 앞의 버전에서는 모든 row를 대상으로 select query를 호출하였었는데, 이 방법에서는
// jpql where문 조건에 in을 활용해서 한 번에 가져온 다음
// 메모리에 Map으로 가져온 다음 메모리에서 매칭을 해서 값을 세팅해준다.
Map<Long, List<OrderItemQueryDto>> orderItemMap = orderItems.stream()
.collect(Collectors.groupingBy(orderItemQueryDto -> orderItemQueryDto.getOrderId()));
result.forEach(o->o.setOrderItems(orderItemMap.get(o.getOrderId())));
return result;
}
'스프링 관련 > 스프링 프레임워크' 카테고리의 다른 글
@RestControllerAdvice, @ExceptionHandler(RuntimeException.class) 이용한 예외처리 분리 (0) | 2022.11.20 |
---|---|
@Transactional 동작 원리 간략 설명 (0) | 2022.09.13 |
API 만들 때 생길 수 있는 문제 (지연 로딩과 조회 성능 최적화) (0) | 2022.08.28 |
springboot test data.sql 넣는 방법 (0) | 2022.08.25 |
타임리프 문법 사용법 (유틸리티 객체 등) 정리 (0) | 2022.05.05 |
댓글