쿼리 방식 선택 권장 순서
1. 우선 엔티티를 DTO로 변환하는 방법을 선택한다.
2. 필요하면 페치 조인으로 성능을 최적화 한다. 대부분의 성능 이슈가 해결된다.
3. 그래도 안되면 DTO로 직접 조회하는 방법을 사용한다.
4. 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용한다
주문+배송정보+회원을 조회하는 API
양방향 연관관계가 걸린 곳은 꼭! 한 곳을 @JsonIgnore 처리해야한다. 안그러면 양쪽을 서로 호출하면서 무한루프발생.
엔티티를 API응답으로 노출하는것보다는 DTO로 변환해서 반환하는 것이 더 좋다.
(실무에서는 List로 바로 반환하지말고 result객체로 한 번 더 감싸주자)
간단한 주문조회를 하는 컨트롤러 메서드.
아래 코드는 지연로딩으로 인해 성능상 이슈가 발생한다.
/**
* 엔티티말고 dto로 할 것.
* 실무에서는 list로 바로 반환하지 말고 result객체로 한 번 감싸주자!
* 이것도 좋은 방법은 아니다. 성능문제 있음.
*/
@GetMapping("/api/v2/simple-orders")
public List<SimpleOrderDto> ordersV2(){
// Order가 2개인데 쿼리는 5개가 나가버림~~~~!
List<Order> orders = orderRepository.findAllByCriteria(new OrderSearch());
List<SimpleOrderDto> result = orders.stream()
.map(o -> new SimpleOrderDto(o))
.collect(Collectors.toList());
return result;
}
페치조인을 활용해서 최적화해주었다
/**
* 패치조인 : 엔티티 다 찍어오는게 단점
* 여기까지 오면 대부분의 성능 이슈가 해결된다.
*/
@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(Collectors.toList());
return result;
}
/**
* 바로 JPA에서 DTO로 끄집어낸다(v3보다 더 성능 최적화 가능)
* v3해도 안되면 DTO를 바로 사용하는 것임.
*/
@GetMapping("/api/v4/simple-orders")
public List<OrderSimpleQueryDto> ordersV4(){
return orderSimpleQueryRepository.findOrderDtos();
}
@Data
static class SimpleOrderDto{
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
// Dto가 엔티티를 파라미터로 받는거는 괜찮음
public SimpleOrderDto(Order order) {
orderId = order.getId();
name = order.getMember().getName(); // LAZY 초기화
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress(); // LAZY 초기화
}
}
}
아래는 repository code
/**
* v3버전 : 엔티티에 대한 순수성 유지중
*/
public List<Order> findAllWithMemberDelivery() {
// 패치조인 : LAZY무시하고 한방쿼리로 다 땡겨온다.
return em.createQuery(
"select o from Order o " +
"join fetch o.member m " +
"join fetch o.delivery d",Order.class
).getResultList();
}
/**
* v4버전 : 재사용성 x, 이 dto 쓸때만 사용 가능. Dto로 조회했기때문에 변경하거나 할 수 없다.
* 장점 : v3보다는 성능 최적화(근데 생각보다 차이 안난다고 함)
* 근데, repository인데 엔티티를 조회하는게 아니다? api스펙이 그냥 들어온것. 논리적으로 계층 깨진것임.
* repository 패키지 밑에 order.simplequery패키지로 따로 뽑음.
* repository 패키지 밑에 order.simplequery패키지로 따로 뽑아서 repository의 순수성을 유지해주자
*/
public List<OrderSimpleQueryDto> findOrderDtos() {
// repository.order.simplequery로 코드 옮겼음.
return null;
}
public List<OrderSimpleQueryDto> findOrderDtos() {
// new 명령어를 사용해서 DTO로 즉시 반환
// 여기선 new 파라미터를(dto생성자 매개변수를) 엔티티로 넘기면 안됨, 엔티티 넘기면 연산자로 인식함
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();
}
굳이 DTO로 즉시반환을 할 필요가 있을까?
'Java > JPA' 카테고리의 다른 글
SpringDataJPA - 메서드 및 유연한 반환타입 (0) | 2024.01.09 |
---|---|
JPA실전 2 - OSIV와 성능 (0) | 2023.12.12 |
JPA 실전2편 - 회원 관련 API (0) | 2023.12.07 |
JPA실전1 정리 (0) | 2023.11.26 |
JPQL - Named 쿼리, 벌크연산 (0) | 2023.11.22 |