聊一聊CriteriaQuery

聊一聊CriteriaQuery

Criteria Query,译为条件查询,可理解为程序式、函数式查询。为什么这样说呢?写SQL语句也是编程,但是,SQL就没有什么类、函数这些编程概念了。而Criteria Query就是把查询用一系列对象、方法的调用组合起来,最终结果也得到一条SQL语句。这样做的好处主要是:不用拼接语句,不用写一句很长的代码。写过SQL的人都知道,一个SQL可能很长,但是通常都不是一口气写下来的,而是一小段一小段的写,然后组合出来的,Criteria Query也是这样一点点的“构建”,符合编程习惯,还便于除错。

Criteria Query其实和JPQL是一样的,只不过通常对于JPQL中的很多概念,一般会很少去注意它们的名称,但是在Criteria Query里,都得用一个一个像Expression、Predicate这样的接口及其方法来表达,所以感觉很复杂,但如果把它和JPQL对照起来看,就容易理解了。《Hibernate实战(第二版)》这本书讲Criteria Query的时候就是这么干的,这里必须赞一下。但是要记住是跟JPQL/HQL对照,而不是跟普通 的SQL对照,因为这两者虽然相似,但又不同,主要差别在于:JPQL是面向对象的,而SQL不是。在SQL中可以很自由的关联和投影(选择结果列),JPQL就不是,虽然也可以选择一序列对象或属性作为结果,但如果投影的结果不是一个实体(Entity)的话,那返回的就是一个Object[],这基本上就失去Persistence的意义了。所以通常JPQL是这样写的:

SELECT i FROM Item i;

对应的Native SQL是这样写的

SELECT * FROM item;

然而,一般的SQL教科书都会建议写明字段列表,一个是语义明确,另一个是减少不必要的数据读取和传输。

JPQL/HQL与Native SQL的另一个重要区别是:Native SQL里面别名是可选的,一般只在复杂的查询需要关联或有关联子查询时定义别名,甚至也可以不定义,因为如果没有别名那表或视图本来的名称就是别名。而在JPQL/HQL里,别名是必须的,这里和Criteria Query对照起来就好理解了,因为必须传递一个参数,别名就是这个参数的变量名(形参)。其实Criteria Query和JPQL先有哪个还真不好说,背后中的实现有可能是把JPQL转化为Criteria Query再实现的。

了解了这两个差别,下面这种怪异的写法就很好理解了:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> criteria = builder.createQuery( Person.class );
Root<Person> root = criteria.from( Person.class );

一开始很不理解为什么createQuery时已经指定了Person.class,后面还要定义一个root并且传给其构建工厂的参数还是Person.class。其实这两处的意义是不一样的,root这里从工厂方法名称来看,要好理解一点,相当于SQL的主表,在Oracle SQL里,会有FROM t1,t2这种同时从两个表里面查询的写法,同样Criteria Query也可以定义两个Root的,当然最好是用Join,实际上Join这个接口就是扩展自Root接口的。而CreateQuery的参数,实际上是指明了查询结果的类型,一般都会是查询实体(主表)的类别,当然也不一定,如果非要查不是实体的东西,比如String也是可以的,当然最后执行查询得到的结果会是List<String>类型的,这里由于只有一个参数,如果查询多个值,又不是实体,那只能传Object[]进去了,查询结果是List<Object[]>类型,还要作类型转换,跟直接用JDBC差不多,没有什么优势,并不实用。CreateQuery这个函数有点像Select子句但还不是Select,只是指明了查询结果的类型,具体的内容还要用Select接口调用来进一步明确。

总结下来就是,SQL可以查的,JPQL也可以查,只是语法不同而已,结合面向对象的特点,这点语法上的差异还是不难理解的。至于CriteriaQuery,由于要用编程语句来表示,会用到很多接口,但这些接口都是与查询中的语法概念一一对应的,结合API资料,参考一下别人写的,熟悉了就好了。

正因为通常用JPQL都是“select 实体 from 实体”,所以Spring Data JPA就把CriteriaQuery封装成了JpaSpecificationExecutor接口,里面定义了findOne、findALL、count等方法,只需要定义一个接口扩展实例化的JpaSpecificationExecutor接口,无需编写实现,像CrudRepository接口一样,Spring会自动提供实现,非常好用。当然,一个查询的最关键部分也就是查询条件,是必须作为参数传递给要调用的方法的,这一部分在Spring被封装成了Specification接口,虽然是只有一个类型参数的模板,但是实现它倒也不是很容易,主要是因为,虽然封装了,但是查询条件是不可能脱离Query存在的,所以要写好Specification接口的实现,必须搞懂或者基本搞懂CriteriaQuery,在Specification接口的唯一虚函数toPredicate中,(Root,CriteriaQuery<?>,CriteriaBuilder)这三个是要作为参数传递的,虽然不用像前面的代码例子那样去写语句生成这些对象,只需要实例化JpaSpecificationExecutor接口,Spring就会创建好这些对象,但是难点从来就不是对象创建而是使用。所以,还是得好好理解CreateQuery是怎样一步步生成Query(语句)的才行。

最后,采用Criteria Query或者其他的编程语句式查询而不是直接写SQL,其实就是为了根据复杂的条件灵活地生成查询条件。Criteria Query里的Predicate(Spring封装成了Specification)是可以组合的,Predicate扩展了Expression<Boolean>,也就是说一个Predicate就是一个布尔表达式,我们可以根据查询条件生成一个一个的简单Predicate然后组合成一个复杂的Predicate。CriteriaBuilder有可变参数的and、or方法可以实现一步一步组合的目的,但是对于实际情况来说,有些查询条件有时需要有时不需要,判断是否需要and或or还是有点啰嗦,而Spring封装的Specification直接有一个and(Specification[])方法,哪怕Specification[]为空都能用。因为一般查询都是用and嘛,这样就方便多了。

粘一段实际代码。

@PostMapping("/user")
    public String listuser(QueryUserForm queryparam,Model model) {
        List<User> users = userRepository.findAll((root, query, builder) -> {
            List<Predicate> predicates = new ArrayList<Predicate>();
            if (!queryparam.getUsername().isEmpty()) {
                predicates.add(builder.like(root.get("username"), "%" + queryparam.getUsername() + "%"));
            }
            if (!"000000".equals(queryparam.getDistrict_code())) {
                predicates.add(builder.equal(root.get("district").<String>get("code"), queryparam.getDistrict_code()));
            }
            if (queryparam.getOrganization_id() != 0) {
                predicates.add(
                        builder.equal(root.get("organization").<Integer>get("id"), queryparam.getOrganization_id()));
            }
            return builder.and(predicates.toArray(new Predicate[predicates.size()]));
        });
        model.addAttribute(users);
        return "manage/usermanage";
    }

这段代码用到了 λ \lambda λ表达式和“闭包”,这样子就不用在Business层引用QueryUserForm对象,虽然它只是一个POJO对象,但因为它是用来接受前端查询参数的,所以把它归属为Web层而不应该放在Business层,同时,也不用在Web层引入Root,CriteriaQuery<?>,CriteriaBuilder这些应该是Business(数据层)的对象。相当于在controller里真的只是写了一些查询条件,应该不算业务逻辑吧,因为又没有改变什么。这个例子也可以当作“闭包”应用的一个具体例子吧。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值