Spring Data JPA 函数的用法

在今天的工作中,有一个分组查询需要按照日期分组统计业务数据。其中有个棘手的问题是业务时间是按照Calendar类型存的,如果是string类型的话,就可以直接截取年-月-日,然后按年-月-日group by就OK了。但是现在,涉及到时间的转换。想了半天,发现CriteriaBuilder里有个function方法,或许可以解决。网上翻了翻资料,没找到很合适的案例,不断尝试了下,问题解决了,现结合一个小例子跟大家分享下。

首先看数据:

Spring Data JPA 函数的用法_第1张图片

一张订单表,有三个字段:订单ID,金额,支付时间,其中time字段在Entity里定义的是Calendar类型的。

需求:一段时间内按日期分组统计订单笔数和金额汇总数。

如果用原生sql,那么非常简单,如下:

Spring Data JPA 函数的用法_第2张图片

一个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 query = cb.createQuery(OrderSum.class);
        Root root = query.from(OrderEntity.class);
        Path timePath = root.get("time");
        Path feePath = root.get("fee");
        List predicateList = new ArrayList();
        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 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 typedQuery = entityManager.createQuery(query);
        typedQuery.setParameter("formatStr", "%Y-%m-%d");
        List 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 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

通过打印出来的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 query = cb.createQuery(OrderSum.class);
        Root root = query.from(OrderEntity.class);
        Path timePath = root.get("time");
        Path feePath = root.get("fee");
        List predicateList = new ArrayList();
        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 typedQuery = entityManager.createQuery(query);
        List 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

结果一样一样的, 通过打印出来的sql发现:substring(cast(orderentit0_.time as char),1,10),直接用cast转成char类型,然后截取了。

总结,其实第一种方式通过构建function适用性更广,如根据其他需求,可以使用DAYOFWEEK(),DAYOFMONTH()等多种函数,只要需要,就可构建。文章有不足之处,敬请斧正。

 

 

 

你可能感兴趣的:(Spring,Data,JPA)