ERPre v2 코드 리팩토링
기존 JPQL 를 사용했을때 장점 2가지>
1. Fetch Join과 countQuery 활용
(N+1 문제 예방 및 페이징 최적화)
@Query(value = "SELECT d FROM Dispatch d " +
"JOIN FETCH d.order o " +
"JOIN FETCH o.customer c " +
"JOIN FETCH d.orderDetail od " +
"JOIN FETCH od.product p " +
"LEFT JOIN FETCH d.warehouse w " +
"WHERE d.dispatchStatus = :dispatchStatus " +
"AND o.orderHStatus = 'approved' " +
"AND d.dispatchDeleteYn = 'N'",
countQuery = "SELECT COUNT(d) FROM Dispatch d " +
"JOIN d.order o " +
"WHERE d.dispatchStatus = :dispatchStatus " +
"AND o.orderHStatus = 'approved' " +
"AND d.dispatchDeleteYn = 'N'")
Page<Dispatch> findByDispatchStatus(@Param("dispatchStatus") String dispatchStatus, Pageable pageable);
- JOIN FETCH를 활용하여 연관된 엔티티(order, customer, orderDetail, product, warehouse)를 한 번의 쿼리로 가져와 N+1 문제를 방지함.
- countQuery를 별도로 작성하여 JOIN FETCH를 사용한 쿼리가 페이징과 함께 안정적으로 동작하도록 함.
→ 일반적으로 JOIN FETCH를 포함한 쿼리는 count 쿼리 실행 시 오류를 발생시킬 수 있기 때문에 별도로 countQuery를 작성하여 해결함.
2. 조회 성능 개선 및 대량 데이터 환경에서 일관된 성능 유지
@Query("SELECT d FROM Dispatch d " +
"JOIN FETCH d.orderDetail od " +
"JOIN FETCH od.product p " +
"LEFT JOIN FETCH p.productDetails pd " +
"LEFT JOIN FETCH p.category c " +
"WHERE d.dispatchNo = :dispatchNo")
Dispatch findByDispatchDetails(@Param("dispatchNo") Integer dispatchNo);
- JOIN FETCH를 통해 orderDetail, product, productDetails, category 데이터를 한 번에 가져와 조회 시 성능을 최적화함.
- LEFT JOIN FETCH를 활용하여 productDetails와 category가 없을 경우에도 데이터를 정상적으로 조회하도록 처리함.
- 특정 dispatchNo를 기반으로 상세 정보를 가져오도록 설계하여, 특정 출고 정보를 조회할 때 최소한의 쿼리 실행을 보장함.
QueryDSL 로 리팩토링>
이전에는 @Query를 사용하여 정적인 JPQL을 작성했는데, 쿼리 확장성과 유지보수성 차원에서 QueryDSL을 사용하면 좋을것 같다.
QueryDSL을 활용한 동적 쿼리 작성
public Page<Dispatch> findByDispatchStatusWithQueryDSL(String dispatchStatus, Pageable pageable) {
QDispatch dispatch = QDispatch.dispatch;
QOrder order = QOrder.order;
QCustomer customer = QCustomer.customer;
QOrderDetail orderDetail = QOrderDetail.orderDetail;
QProduct product = QProduct.product;
QWarehouse warehouse = QWarehouse.warehouse;
// ✅ 조회 쿼리 - fetchJoin 최적화
List<Dispatch> dispatches = queryFactory
.selectFrom(dispatch)
.join(dispatch.order, order).fetchJoin()
.join(order.customer, customer).fetchJoin()
.join(dispatch.orderDetail, orderDetail).fetchJoin()
.join(orderDetail.product, product).fetchJoin()
.leftJoin(dispatch.warehouse, warehouse) // fetchJoin 제거 (불필요한 데이터 로딩 방지)
.where(dispatch.dispatchStatus.eq(dispatchStatus)
.and(order.orderHStatus.eq("approved"))
.and(dispatch.dispatchDeleteYn.eq("N")))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
// ✅ count 쿼리 - fetchJoin 제거하여 최적화
long total = queryFactory
.select(dispatch.count())
.from(dispatch)
.join(dispatch.order, order) // 불필요한 조인 제거하여 성능 향상
.where(dispatch.dispatchStatus.eq(dispatchStatus)
.and(order.orderHStatus.eq("approved"))
.and(dispatch.dispatchDeleteYn.eq("N")))
.fetchOne();
return new PageImpl<>(dispatches, pageable, total);
}
📌 비교
개선점 | 기존 코드 | 수정 코드 |
불필요한 fetchJoin() 제거 | 모든 JOIN에 fetchJoin() 적용 | warehouse는 leftJoin()만 적용하여 최적화 |
countQuery에서 불필요한 JOIN 제거 | 모든 관계를 조인 | order만 조인하여 성능 향상 |
Slice<T> 사용 | countQuery 실행 | countQuery 없이 next 여부만 체크 |
📌 결론
✅ QueryDSL 코드에서 fetchJoin()을 최적화하여 메모리 사용량 감소&성능 향상
✅ count 쿼리 최적화를 통해 JOIN을 줄여 성능 개선.
✅ Slice<T>를 활용하면 countQuery 없이 페이징 가능.
✅ 불필요한 데이터 로드를 줄여 대량 데이터에서도 안정적인 성능 유지 가능
'개발인생 > Project' 카테고리의 다른 글
페이징처리 프로젝트 구현(Oracle) (0) | 2025.03.17 |
---|---|
페이징처리 프로젝트 구현(React) (0) | 2025.03.17 |
페이징처리 - Pagination.js + Pageable (0) | 2025.03.17 |
Spring Security 설정 완벽 분석 - 보안 설정 가이드 (2) | 2025.03.05 |
JSP/Servlet 프로젝트와 React+Spring Boot 프로젝트 비교 (1) | 2025.02.19 |