본문 바로가기
JAVA/JPA

[JPA] JPA 성능 최적화 #1 - fetch join

by 개미가되고싶은사람 2024. 12. 10.

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