1. 程式人生 > >Spring Data JPA 函式的用法

Spring Data JPA 函式的用法

在今天的工作中,有一個分組查詢需要按照日期分組統計業務資料。其中有個棘手的問題是業務時間是按照Calendar型別存的,如果是string型別的話,就可以直接擷取年-月-日,然後按年-月-日group by就OK了。但是現在,涉及到時間的轉換。想了半天,發現CriteriaBuilder裡有個function方法,或許可以解決。網上翻了翻資料,沒找到很合適的案例,不斷嘗試了下,問題解決了,現結合一個小例子跟大家分享下。

首先看資料:

一張訂單表,有三個欄位:訂單ID,金額,支付時間,其中time欄位在Entity裡定義的是Calendar型別的。

需求:一段時間內按日期分組統計訂單筆數和金額彙總數。

如果用原生sql,那麼非常簡單,如下:

一個sql就搞定了,但是現在用spring data JPA,就需要研究下了。關鍵點是將時間轉換為string型別,然後擷取yyyy-mm-dd,按照其分組統計就可以了。

廢話不多說,上程式碼:

public void queryOrder(OrderParam orderParam) {
        Calendar start = orderParam.getStart();
        Calendar end = orderParam.getEnd();
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        //OrderSum指定了查詢結果返回至自定義物件
        CriteriaQuery<OrderSum> query = cb.createQuery(OrderSum.class);
        Root<OrderEntity> root = query.from(OrderEntity.class);
        Path<Calendar> timePath = root.get("time");
        Path<Integer> feePath = root.get("fee");
        List<Predicate> predicateList = new ArrayList<Predicate>();
        if (start != null) {
            predicateList.add(cb.greaterThanOrEqualTo(timePath, start));
        }
        if (end != null) {
            end.add(Calendar.DATE, 1);
            predicateList.add(cb.lessThan(timePath, end));
        }
        Predicate[] predicates = new Predicate[predicateList.size()];
        predicates = predicateList.toArray(predicates);
        //加上where條件
        query.where(predicates);
        //指定查詢項,select後面的東西
        Expression<String> timeStr = cb.function("DATE_FORMAT", String.class, timePath, cb.parameter(String.class, "formatStr"));
        query.multiselect(timeStr, cb.count(root).as(Integer.class), cb.sum(feePath));
        query.groupBy(timeStr);
        query.orderBy(cb.asc(timeStr));
        TypedQuery<OrderSum> typedQuery = entityManager.createQuery(query);
        typedQuery.setParameter("formatStr", "%Y-%m-%d");
        List<OrderSum> result = typedQuery.getResultList();
        for (OrderSum orderSum : result) {
            //列印查詢結果
            System.out.println(orderSum.toString());
        }
    }

 其中OrderParam是定義的一個查詢條件類,其結構如下:

public class OrderParam {
    private Calendar start;
    private Calendar end;

    public Calendar getStart() {
        return start;
    }

    public void setStart(Calendar start) {
        this.start = start;
    }

    public Calendar getEnd() {
        return end;
    }

    public void setEnd(Calendar end) {
        this.end = end;
    }
}

OrderSum是自定義的一個查詢結果返回實體,其結構如下:

public class OrderSum {
    private String date;
    private Integer count;
    private Long totalFee;

    //需要有個構造方法
    public OrderSum(String date, Integer count, Long totalFee) {
        this.date = date;
        this.count = count;
        this.totalFee = totalFee;
    }

    //此處省略了get、set、toString方法。
}

 重點說明:

Expression<String> timeStr = cb.function("DATE_FORMAT", String.class, timePath, cb.parameter(String.class, "formatStr"));這行程式碼就是想構造MySQL的DATE_FORMAT(date,format)函式。函式的名字DATE_FORMAT已經有了,timePath就是要被轉換的物件。cb.parameter(String.class, "formatStr")是為了加上一個引數,typedQuery.setParameter("formatStr", "%Y-%m-%d"),為引數賦值"%Y-%m-%d"。最終達到實現DATE_FORMAT(time,'%Y-%m-%d') 的目的。好了,看執行結果:

Hibernate: 
    select
        date_format(orderentit0_.time,
        ?) as col_0_0_,
        cast(count(orderentit0_.id) as signed) as col_1_0_,
        sum(orderentit0_.fee) as col_2_0_ 
    from
        t_order orderentit0_ 
    where
        orderentit0_.time>=? 
        and orderentit0_.time<? 
    group by
        date_format(orderentit0_.time,
        ?) 
    order by
        date_format(orderentit0_.time,
        ?) asc

OrderSum{date='2018-08-18', count=1, totalFee=4}
OrderSum{date='2018-08-19', count=5, totalFee=18}
OrderSum{date='2018-08-20', count=4, totalFee=26}
OrderSum{date='2018-08-21', count=5, totalFee=13}
OrderSum{date='2018-08-22', count=5, totalFee=35}
OrderSum{date='2018-08-23', count=3, totalFee=9}

通過打印出來的sql,我們發現,第一個?號賦值上'%Y-%m-%d'後,就達到了我們想要的 date_format(orderentit0_.time,'%Y-%m-%d')效果。

後來,發現另一種方法也可以實現,貌似更簡單:

public void queryOrder(OrderParam orderParam) {
        Calendar start = orderParam.getStart();
        Calendar end = orderParam.getEnd();
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        //OrderSum指定了查詢結果返回至自定義物件
        CriteriaQuery<OrderSum> query = cb.createQuery(OrderSum.class);
        Root<OrderEntity> root = query.from(OrderEntity.class);
        Path<Calendar> timePath = root.get("time");
        Path<Integer> feePath = root.get("fee");
        List<Predicate> predicateList = new ArrayList<Predicate>();
        if (start != null) {
            predicateList.add(cb.greaterThanOrEqualTo(timePath, start));
        }
        if (end != null) {
            end.add(Calendar.DATE, 1);
            predicateList.add(cb.lessThan(timePath, end));
        }
        Predicate[] predicates = new Predicate[predicateList.size()];
        predicates = predicateList.toArray(predicates);
        //加上where條件
        query.where(predicates);
        //指定查詢項,select後面的東西
        query.multiselect(cb.substring(timePath.as(String.class), 1, 10), cb.count(root).as(Integer.class), cb.sum(feePath));
        query.groupBy(cb.substring(timePath.as(String.class), 1, 10));
        query.orderBy(cb.asc(cb.substring(timePath.as(String.class), 1, 10)));
        TypedQuery<OrderSum> typedQuery = entityManager.createQuery(query);
        List<OrderSum> result = typedQuery.getResultList();
        for (OrderSum orderSum : result) {
            //列印查詢結果
            System.out.println(orderSum.toString());
        }
    }

cb.substring(timePath.as(String.class),直接轉成string型別,然後擷取年-月-日。執行結果如下:

Hibernate: 
    select
        substring(cast(orderentit0_.time as char),
        1,
        10) as col_0_0_,
        cast(count(orderentit0_.id) as signed) as col_1_0_,
        sum(orderentit0_.fee) as col_2_0_ 
    from
        t_order orderentit0_ 
    where
        orderentit0_.time>=? 
        and orderentit0_.time<? 
    group by
        substring(cast(orderentit0_.time as char),
        1,
        10) 
    order by
        substring(cast(orderentit0_.time as char),
        1,
        10) asc
OrderSum{date='2018-08-18', count=1, totalFee=4}
OrderSum{date='2018-08-19', count=5, totalFee=18}
OrderSum{date='2018-08-20', count=4, totalFee=26}
OrderSum{date='2018-08-21', count=5, totalFee=13}
OrderSum{date='2018-08-22', count=5, totalFee=35}
OrderSum{date='2018-08-23', count=3, totalFee=9}

結果一樣一樣的, 通過打印出來的sql發現:substring(cast(orderentit0_.time as char),1,10),直接用cast轉成char型別,然後截取了。

總結,其實第一種方式通過構建function適用性更廣,如根據其他需求,可以使用DAYOFWEEK(),DAYOFMONTH()等多種函式,只要需要,就可構建。文章有不足之處,敬請斧正。