이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.
API를 만들 때 엔티티 자체를 리턴하는 방식을 절대로 사용하면 안된다.
기본적으로 엔티티를 DTO로 바꾸어서 리턴해야 한다.
또한 트래픽이 낮다면 굳이 성능 최적화가 필요 없겠지만, 사용자가 늘어난다면 성능 최적화를 고려해야한다.
성능 최적화 순서
- 엔티티를 DTO로 변환하는 방법을 선택
- 필요하면 FETCH 조인으로 성능을 최적화 한다. 대부분의 성능 이슈가 해결
- 그래도 안되면 DTO로 직접 조회하는 방법을 사용
- 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용
엔티티를 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(); //Lazy 초기화
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress(); //Lazy 초기화
}
}
/**
* V2. 엔티티를 조회해서 DTO로 변환(fetch join 사용X) * - 단점: 지연로딩으로 쿼리 N번 호출
*/
@GetMapping("/api/v2/simple-orders")
public List<SimpleOrderDto> ordersV2() {
List<Order> orders = orderRepository.findAllByString(new OrderSearch());
List<SimpleOrderDto> result = orders.stream()
.map(o -> new SimpleOrderDto(o))
.collect(toList());
return result;
}
이 방식의 가장 큰 문제점은 N+1 문제가 발생한다.
즉, DB에 쿼리가 예상했던 것 보다 많이 날라가는 문제가 발생한다.
FETCH JOIN으로 성능 최적화
/**
* 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();
}
이 방식은 N+1 문제가 해결되지만 필요 없을 수도 있는 컬럼마저 가져온다는 단점이 있다.
DTO로 직접 조회하는 방법
이 방법은 JPQL을 통해서 원하는 컬럼 정보만 가져오는 쿼리를 작성하고, 작성한 쿼리에 맞는 DTO 객체를 반환하는 방법이다.
성능이 확실히 올라가긴 하지만, 특수한 경우가 아니라면 굳이 이렇게까지 안해도 된다.
/**
* V4. JPA에서 DTO로 바로 조회
* - 쿼리 1번 호출
* - select 절에서 원하는 데이터만 선택해서 조회
*/
@GetMapping("/api/v4/simple-orders")
public List<OrderSimpleQueryDto> ordersV4() {
return orderSimpleQueryRepository.findOrderDtos();
}
@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;
}
}
@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();
}
}
- 일반적인 SQL을 사용할 때 처럼 원하는 값을 선택해서 조회
- new 명령어를 사용해서 JPQL의 결과를 DTO로 즉시 변환
- SELECT 절에서 원하는 데이터를 직접 선택하므로 DB 애플리케이션 네트워크 용량 최적화(생각보다 미비)
- 리포지토리 재사용성 떨어짐, API 스펙에 맞춘 코드가 리포지토리에 들어가는 단점
'Java Category > JPA' 카테고리의 다른 글
[Spring Data JPA] Spring Data JPA (0) | 2024.08.12 |
---|---|
[JPA] 컬렉션 조회 최적화(OneToMany) (0) | 2024.08.11 |
[JPA] 병합(Merge)과 변경 감지(Dirty Checking) (0) | 2024.08.05 |
[JPA] JPQL 고급 (0) | 2024.08.03 |
[JPA] JPQL 기본 문법 (0) | 2024.07.29 |