복잡한 통계 쿼리를 사용할 때는 네이티브 쿼리를 사용하고는 한다. 그리고 그 결과를 DTO에 매핑하기 위해 JPA에서 제공하는 natvieQuery를 사용해도 되는 데 DTO매핑이 쉽지 않다. 그래서 QLRM을 사용하는 방법을 소개하겠다. QLRM을 사용하면 DTO 매핑이 매우 쉬워진다.

일단 해당 라이브러리를 추가한다.

//https://github.com/72services/qlrm
implementation 'org.qlrm:qlrm:4.0.1'

그리고는 해당 repository에서 바로 사용하면 된다.

QLRM은 JapResultMapper를 제공하는데 두개의 메소드를 제공한다.

  • uniqueResult : 단일 ResultSet를 처리한다.
  • list : 리스트형 ResultSet를 처리한다.
    @PersistenceContext
    private EntityManager em;

    public OrderStatisticDto getOrderStatisticsDto(String startDate, String endDate) {
        String sql = new StringBuilder()
                .append(" SELECT")
                .append(" COUNT(O.ORDER_ID) AS totalCount")
                .append(" ,SUM(CASE WHEN O.ORDER_STATUS = 'WAITING' THEN 1 ELSE 0 END ) AS waitingCount")
                .append(" ,SUM(CASE WHEN O.ORDER_STATUS = 'DOING' THEN 1 ELSE 0 END ) AS doingCount")
                .append(" ,SUM(CASE WHEN O.ORDER_STATUS = 'DONE' THEN 1 ELSE 0 END ) AS doneCount")
                .append(" FROM ORDERS AS O")
                .append(" WHERE O.ORDER_DATE >= :startDate AND O.ORDER_DATE <= :endDate")
                .toString();

        JpaResultMapper jpaResultMapper = new JpaResultMapper();

        Query nativeQuery = em.createNativeQuery(sql)
                .setParameter("startDate", startDate)
                .setParameter("endDate", endDate);

        return jpaResultMapper.uniqueResult(nativeQuery, OrderStatisticDto.class);
    }

네이티비 쿼리에서 얻으려는 결과를 dto와 바로 매핑처리하면 된다.

    @Getter
    @Setter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class OrderStatisticsDto {
        @JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd")
        private Date orderDate;
        private Long totalCount;
        private BigDecimal waitingCount;
        private BigDecimal doingCount;
        private BigDecimal doneCount;
    }

주의할 점은 JpaResultMapper에서 쿼리에 사용된 COUNT 등은 DTO에서 Long으로 처리되지만, SUM, AVG 등과 같은 데이터베이스 함수를 사용하는 경우 BigDecimal로 가져와야 한다.

    public List<OrderStatisticsDto> getOrderStatisticsDtoList(String startDate, String endDate) {
        String sql = new StringBuilder()
                .append(" SELECT")
                .append(" O.ORDER_DATE AS orderDate")
                .append(" ,COUNT(O.ORDER_ID) AS totalCount")
                .append(" ,SUM(CASE WHEN O.ORDER_STATUS = 'WAITING' THEN 1 ELSE 0 END ) AS waitingCount")
                .append(" ,SUM(CASE WHEN O.ORDER_STATUS = 'DOING' THEN 1 ELSE 0 END ) AS doingCount")
                .append(" ,SUM(CASE WHEN O.ORDER_STATUS = 'DONE' THEN 1 ELSE 0 END ) AS doneCount")
                .append(" FROM ORDERS AS O")
                .append(" WHERE O.ORDER_DATE >= :startDate AND O.ORDER_DATE <= :endDate")
                .append(" GROUP BY O.ORDER_DATE")
                .toString();

        JpaResultMapper jpaResultMapper = new JpaResultMapper();

        Query nativeQuery = em.createNativeQuery(sql)
                .setParameter("startDate", startDate)
                .setParameter("endDate",endDate);

        return jpaResultMapper.list(nativeQuery, OrderStatisticsDto.class);
    }

위에서 처럼 여러개의 resultSet를 가져올 경우 jpaResultMapper.list로 처리하면 된다.

이렇게 하면 Native 쿼리를 DTO에 매핑하는 것이 매우 간단해 진다.