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()等多种函数,只要需要,就可构建。文章有不足之处,敬请斧正。

 

 

 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Spring Data JPA的@Query注解可以在抽象方法上使用。该注解允许我们直接编写自定义的SQL查询语句,以替代默认的基于方法命名规则的查询。 使用@Query注解,我们可以在抽象方法上定义自己的查询语句。可以通过在注解编写原生的SQL查询语句或者使用JPQL查询语句来完成。 当使用原生的SQL查询语句时,我们需要设置nativeQuery参数为true。这样Spring Data JPA就会将查询结果与实体类进行映射。 如果使用JPQL查询语句,我们可以通过在查询语句引用实体类和其属性来构建查询语句。Spring Data JPA会根据查询语句的返回类型自动进行结果映射。 在@Query注解,我们还可以使用命名参数或者索引参数来指定查询参数。通过在查询方法的参数前添加@Param注解,我们可以将方法参数与查询参数进行映射。 除了定义查询语句,@Query注解还可以指定查询的排序方式、分页和锁定等。我们可以使用关键字ORDER BY来设置排序字段,使用关键字LIMIT和OFFSET来设置分页查询的起始位置和返回记录数。同时,我们还可以使用关键字FOR UPDATE来设置查询结果的锁定。 通过在抽象方法上使用@Query注解,我们可以实现更加灵活和复杂的查询需求。它为我们提供了一种强大的方式来利用Spring Data JPA进行自定义查询,以满足特定业务场景的需求。 ### 回答2: Spring Data JPA提供了一个 @Query 注解,可以在抽象方法上使用。 @Query 注解用于在 Repository 接口定义查询方法的具体查询语句。通过使用 @Query 注解,我们可以将自定义的 JPQL 或者 SQL 语句与方法绑定在一起。 使用 @Query 注解有以下几个优点: 1. 灵活性:可以使用自定义的查询语句,满足特定的数据查询需求。 2. 类型安全:由于使用了命名参数或者索引参数,可以避免 SQL 注入等安全问题。 3. 提高代码可读性:通过在方法上添加 @Query 注解,可以直接看到方法的具体查询是如何实现的,提高代码的可读性和可维护性。 4. 内置分页支持:可以在 @Query 注解使用内置的分页查询方法,如 Pageable 和 Sort。 使用 @Query 注解时,可以将查询语句写在注解的 value 属性。语句可以是原生的 SQL 语句,也可以是 JPQL 查询语句。在查询语句,可以使用实体类的属性名来代替数据库表字段名。 另外,@Query 注解还支持使用命名参数和索引参数。命名参数使用冒号(:)加参数名的方式,在方法参数使用 @Param 注解指定参数名。索引参数使用问号(?)加索引位置的方式。 需要注意的是,使用 @Query 注解时,方法的返回类型可以是具体的实体类,也可以是包装类、List、Set 或者其他集合类。如果需要分页查询,返回类型可以是 Page 或者 Slice 类型。 总的来说,通过在抽象方法使用 Spring Data JPA 的 @Query 注解,可以更加灵活地定义自定义查询,提高代码的可读性和可维护性。 ### 回答3: Spring Data JPA提供了 @Query 注解,它可以用在抽象方法上,用于自定义查询语句。 使用 @Query 注解,我们可以在抽象方法上定义自己的JPQL(Java Persistence Query Language)或SQL查询语句。通过在注解定义查询语句,我们可以灵活地执行各种复杂的查询操作。 在定义查询语句时,我们可以使用实体类的属性名来引用实体的字段,并且可以使用 JPA 提供的各种查询关键字和函数进行组合。除此之外,我们还可以使用一些特殊的 JPA 提供的关键字,如:distinct、is not null、is null、order by等。 在定义查询语句时,@Query 注解还支持使用命名参数和位置参数两种方式来传递参数。命名参数使用 :paramName 的方式来引用参数,而位置参数使用 ?1、?2 等符号来引用参数。我们可以根据实际情况选择适合的参数传递方式。 除了定义查询语句,@Query 注解还有其他一些属性。例如,我们可以使用 @Modifying 注解来告诉 Spring Data JPA 这个查询是一个更新操作,需要通过 @Transactional 注解来开启事务。另外,还可以使用 @Query 注解的 nativeQuery 属性来指示是否执行原生 SQL 查询。 总之,通过在抽象方法上使用 @Query 注解,我们可以自定义我们需要的查询语句,使得我们可以方便地执行各种复杂的查询操作。这样,在使用 Spring Data JPA 进行数据访问时,我们就能够更加灵活地控制查询操作,并且减少重复代码的编写。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值