JPQL에서 fetch join을 사용하면 관련된 모든 속성이 select 절에 들어옵니다. 그리고 select 절에서는 fetch join을 사용한 엔티티의 속성을 직접 명시할 수 없습니다. 이로 인해 select 절에 많은 속성이 포함되면 성능 저하가 발생할 수 있습니다. 그렇다고 지연 로딩에서는 N+1 문제가 발생할 수 있기 때문에 fetch join을 사용해야 합니다. 그럼 fetch join을 사용하되 select 절에 원하는 속성만 사용하는 방법에 대해서 알아보겠습니다.
최적화 전
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d", Order.class)
.getResultList();
-- repo --
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();
}
-controller-
@GetMapping("/api/orders")
public ResponseResult orders() {
List<Order> orders = orderRepository.findAllWithMemberDelivery();
List<SimpleOrderDto> result = orders.stream()
.map(o -> new SimpleOrderDto(o))
.collect(Collectors.toList());
return new ResponseResult<>(result);
}
최적화 후 - JQPL 사용
JPQL를 엔티티로 반환하지 말고 원하는 속성만 있는 DTO로 반환하면 fetch join을 사용하되 select 절에 원하는 속성만 사용할 수 있습니다.
DTO
package jpa.repository.order.query;
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;
}
}
JPQL에서 DTO로 반환할 때, DTO의 생성자 매개변수에 엔티티를 사용할 수 있지만 불필요한 활동이 있을 수 있어서 매개변수를 하나하나 직접 넣는게 좋습니다.
Controller
@GetMapping("/api/orders")
public List<OrderSimpleQueryDto> orders() {
return orderRepository.findByDto();
}
Repository
public List<OrderSimpleQueryDto> findByDto() {
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();
}
JPQL 특징 중 하나인 new 키워드를 통해서 클래스를 select 절에서 사용할 수 있다는 특징을 가지고 DTO를 select 절에서 사용할 수 있습니다.이때 DTO의 생성자 매개변수 위치와 JPQL 쿼리에서 작성한 DTO 생성자 순서와 일치해야 합니다.
최적화 결과
엔티티 대신 DTO로 반환한다고 해서 큰 성능 향상을 기대하기는 어렵습니다. 다만 select 절에 많은 속성이 포함될 경우에는 성능 향상을 기대할 수 있습니다. 그렇다고 fetch join을 사용할 때 항상 DTO로 반환하는 방식은 좋지 않습니다. 왜냐하면 JPQL을 사용하여 DTO로 반환할 때는 select 절에 들어가는 속성이 DTO에 맞춰져야 하므로, 이로 인해 코드 재사용성이 떨어질 수 있습니다.
참고자료
https://innovation123.tistory.com/237
[JPA / Spring] JPQL을 이용해서 Entity가 아닌 DTO를 반환하는 방법
예제 소스코드(Google Drive)https://drive.google.com/file/d/1YrsS9-sjHM4QSLhe-5GZsvcfVlvMI_Z9/view?usp=sharingapplication.property 파일 수정해서 DB 연결해서 테스트해 보실 수 있습니다. (소스코드에서는 xxx처리) JPQL을 이
innovation123.tistory.com
'JAVA > JPA' 카테고리의 다른 글
[JPA] JPA 컬렉션 조회 성능 최적화 #3 - JPA에서 DTO 직접 반환 (3) | 2024.12.14 |
---|---|
[JPA] JPA 컬렉션 조회 성능 최적화 #2 - @BatchSize (2) | 2024.12.14 |
[JPA] 순환참조 문제해결 - 양방향 매핑 문제점 (0) | 2024.12.09 |
[JPA] Named 쿼리 (0) | 2024.12.03 |
[JPA] 벌크(Bulk) 연산 (1) | 2024.12.02 |