김영한님의 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 를 보고 기록한 글입니다.
기본적으로 페치 타입 LAZY를 사용한다는 가정 하에, 아래부터 한 단계씩 최적화하는 방법을 기록하려고 한다.
참고 테이블
( 참고: 회원이 주문을 하기 때문에, 회원이 주문리스트를 가지는 것은 얼핏 보면 잘 설계한 것 같지만, 객체 세상은 실제 세계와는 다르다.
실무에서는 회원이 주문을 참조하지 않고, 주문이 회원을 참조하는 것으로 충분하다.
여기 서는 일대다, 다대일의 양방향 연관관계를 설명하기 위해서 추가했다. )
우선 첫번째 코드는, 기본적으로 제공하는 findAll()을 사용하는 코드다.
/**
* V1. 엔티티를 조회해서 DTO로 변환(fetch join 사용X)
* - 단점: 지연로딩으로 쿼리 N번 호출
*/
@GetMapping("/api/v1/simple-orders")
public List<SimpleOrderDto> ordersV1() {
List<Order> orders = orderRepository.findAllByString(new OrderSearch());
List<SimpleOrderDto> result = orders.stream()
.map(o -> new SimpleOrderDto(o))
.collect(toList());
return result;
}
DTO
@Data
static class SimpleOrderDto {
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
public SimpleOrderDto(Order order) {
orderId = order.getId();
name = order.getMember().getName();
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress();
}
}
위 코드를 실행한다면, 쿼리가 총 1 + N + N번 실행된다.
- order 조회 1번(order 조회 결과 수가 N이 된다.)
- order -> member 지연 로딩 조회 N 번
- order -> delivery 지연 로딩 조회 N 번
- 예) order의 결과가 4개면 최악의 경우 1 + 4 + 4번 실행된다.(최악의 경우)
- 지연로딩은 영속성 컨텍스트에서 조회하므로, 이미 조회된 경우 쿼리를 생략한다.
페치 조인을 써서 최적화한 두 번째 코드
/**
* V3. 엔티티를 조회해서 DTO로 변환(fetch join 사용O)
* - fetch join으로 쿼리 1번 호출
* 참고: fetch join에 대한 자세한 내용은 JPA 기본편 참고(정말 중요함)
*/
@GetMapping("/api/v3/simple-orders")
public List<SimpleOrderDto> ordersV3() {
List<Order> orders = orderRepository.findAllWithMemberDelivery();
List<SimpleOrderDto> result = orders.stream()
.map(o -> new SimpleOrderDto(o))
.collect(toList());
return result;
}
OrderRepository - 추가 코드
public List<Order> findAllWithMemberDelivery() {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d", Order.class)
.getResultList();
}
- 엔티티를 페치 조인 (fetch join)을 사용해서 쿼리 1번에 조회
- 페치 조인으로 order -> member , order -> delivery 는 이미 조회 된 상태 이므로 지연로딩X
마지막은 조회 쿼리 필드 수를 줄이는 방법으로, JPA에서 DTO로 바로 조회하는 코드다.
private final OrderSimpleQueryRepository orderSimpleQueryRepository; //의존관계 주입
/**
* V4. JPA에서 DTO로 바로 조회
* - 쿼리 1번 호출
* - select 절에서 원하는 데이터만 선택해서 조회
*/
@GetMapping("/api/v4/simple-orders")
public List<OrderSimpleQueryDto> ordersV4() {
return orderSimpleQueryRepository.findOrderDtos();
}
OrderSimpleQueryRepository 조회 전용 리포지토리
@Repository
@RequiredArgsConstructor
public class OrderSimpleQueryRepository {
private final EntityManager em;
public List<OrderSimpleQueryDto> findOrderDtos() {
return em.createQuery(
"select new jpabook.jpashop.repository.order.simplequery.OrderSimpleQueryDto(o.id, m.name, o.orderDate, o.status, d.address)" +
" from Order o" +
" join o.member m" +
" join o.delivery d", OrderSimpleQueryDto.class)
.getResultList();
}
}
OrderSimpleQueryDto 리포지토리에서 DTO 직접 조회
@Data
public class OrderSimpleQueryDto {
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
public OrderSimpleQueryDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address) {
this.orderId = orderId;
this.name = name;
this.orderDate = orderDate;
this.orderStatus = orderStatus;
this.address = address;
}
}
- 일반적인 SQL을 사용할 때 처럼 원하는 값을 선택해서 조회
- new 명령어를 사용해서 JPQL의 결과를 DTO로 즉시 변환
- SELECT 절에서 원하는 데이터를 직접 선택하므로 DB 애플리케이션 네트웍 용량 최적화(생각보다 미비)
- 리포지토리 재사용성 떨어짐, API 스펙에 맞춘 코드가 리포지토리에 들어가는 단점
마지막 두 개의 최적화 (엔티티를 DTO로 변환하거나, DTO로 바로 조회하는 두가지 방법) 는 각각 장단점이 있다.
둘중 상황에 따라서 더 나은 방법을 선택하면 된다.
그러나 dto조회와 엔티티 조회 시 쿼리 필드 수가 많이 차이나지 않는다면,
엔티티로 조회하면 리포지토리 재사용성도 좋고, 개발도 단순해진다.
따라서 권장하는 방법은 다음과 같다.
쿼리 방식 선택 권장 순서
- 우선 엔티티를 DTO로 변환하는 방법을 선택한다.
- 필요하면 페치 조인으로 성능을 최적화 한다. -> 대부분의 성능 이슈가 해결된다.
- 그래도 안되면 DTO로 직접 조회하는 방법을 사용한다.
- 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용한다.
'대충 넘어가지 않는 습관을 위한 기록' 카테고리의 다른 글
@Mapping 활용법 - boolean (0) | 2024.06.01 |
---|---|
Querydsl - DTO 반환 (0) | 2024.05.02 |
JPA 기록 - 객체지향 쿼리 언어 JPQL (0) | 2024.04.20 |
JPA 기록 - 값 타입 (임베디드 타입, 값 타입 컬렉션) (0) | 2024.04.17 |
JPA 기록 - 프록시와 연관관계 관리 (+ 지연 로딩, 영속성 전이) (0) | 2024.04.14 |