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()等多種函式,只要需要,就可構建。文章有不足之處,敬請斧正。