编程修炼之hibernate JPA之动态查询:Specification与Example的使用

官网

概述

JPA从2开始引入了criteria的api来编程式构建查询,对于变化参数的jpa编写提供了非常大的便利。Specitication并不能在查询便利上有多少优势,但是在动态添加条件进行多维组合的场景有非常好的效果。

Specification

jpa的接口要继承JpaSpecificationExecutor

public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor {}

查询就可以使用

List<T> findAll(Specification<T> spec);

jpa

jpa的QBC最常见的是 findAll(Specification specification)

public class CustomerSpecs {
  public static Specification<Customer> isLongTermCustomer() {
    return (root, query, builder) -> {
      LocalDate date = LocalDate.now().minusYears(2);
      return builder.lessThan(root.get(Customer_.createdAt), date);
    };
  }
  public static Specification<Customer> hasSalesOfMoreThan(MonetaryAmount value) {
    return (root, query, builder) -> {
      // build query here
    };
  }
}

Customer_ 是JPA元模型。Customer_.createdAt表示Customer对象有createAt属性(Date类型)
进行查询组合:

MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
  isLongTermCustomer().or(hasSalesOfMoreThan(amount)));

在Specification中,buider相当于进行条件判断的部分,root实际上相当于表,从表中挑选字段,添加条件,然后返回判断标准Specification,交给数据库去查询。

EntityManager

另一种方式是从 EntityManager 出发:

		// 第一步: 获取条件构造器
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        // 第二步: 构造标准查询API,这里使用Object[]而不是使用User是因为下面有别名,函数的情况,所以用实体无法接收,还有很多的方式
        CriteriaQuery<Object[]> criteriaQuery = criteriaBuilder.createQuery(Object[].class);
        // 第三步: 指定查询的根实体,说白了就是主表内容,Root就表示主表的字段信息
        Root<User> root = criteriaQuery.from(User.class);
        // 第四步(可选): 多表关联,User表与IdCard表关联,使用的都是属性名,自动设置连接条件
        Join<User, IdCard> join = root.join("idCard", JoinType.LEFT);
        // 如果设置一对多,或者多对多,使用joinCollection
        // Join<User, Address> join = root.joinCollection("idCard", JoinType.LEFT);
        // 第四-五步: 要查询的字段汇总
        // 查询所有
        // criteriaQuery.select(root);
        // 查询指定字段和使用聚合函数并且取别名
 
        // 给字段取别名,记得接受结果的返回值的属性需要对应上
        // 注意: 控制台打印的SQL没有取别名,Hibernate生成的SQL语句不会包含Criteria API中设置的别名
        Selection<String> name = root.get("name").as(String.class).alias("name");
        Selection<Integer> age = root.get("age").as(Integer.class).alias("age");
        // 给聚合函数取别名
        Expression<Long> count = criteriaBuilder.count(root.get("age"));
        criteriaBuilder.construct(Long.class, count.alias("count"));
        // 查询字段,其中,root表示主表select root.*,join表示关联表select join.*
        criteriaQuery.multiselect(root, join, name, age, count);
 
        // 第五步: 构建条件并设置条件,tip: 下面criteriaQuery操作实际上不区分先后顺序
        // 如果要设置主表的字段条件,使用root.get("字段名")
        // 如果要设置关联表的字段条件,join.get("字段名")
        Predicate rootIsDelete = criteriaBuilder.isFalse(join.get("deleted"));
        Predicate idCardDelete = criteriaBuilder.isFalse(root.get("deleted"));
        // 如果要使用and,或or,直接使用conjunction,disjunction,构建一个空条件Predicate,然后在进行拼接和and,or方法是一样的
        Predicate andEmptyPredicate = criteriaBuilder.conjunction();
        // 构造条件
        Predicate nameEqualPredicate = criteriaBuilder.equal(root.get("name"), "luck");
        Predicate ageGePredicate = criteriaBuilder.ge(root.get("age"), 10);
        // 将构造条件统一存入到空的Predicate,并且他们使用and连接
        // user.name = 'luck' and user.age >= 10
        andEmptyPredicate.getExpressions().add(nameEqualPredicate);
        andEmptyPredicate.getExpressions().add(ageGePredicate);
 
        // 上面两行代码等于下面一行代码
        // user.name = 'luck' and user.age >= 10
        Predicate andPredicate = criteriaBuilder.and(nameEqualPredicate, ageGePredicate);
 
        // 将构造条件统一存入到空的Predicate,并且他们使用or连接
        // user.name = 'luck' or user.age >= 10
        Predicate orEmptyPredicate = criteriaBuilder.disjunction();
        orEmptyPredicate.getExpressions().add(nameEqualPredicate);
        orEmptyPredicate.getExpressions().add(ageGePredicate);
 
        // 上面两行代码等于下面一行代码
        // user.name = 'luck' or user.age >= 10
        Predicate orPredicate = criteriaBuilder.or(nameEqualPredicate, ageGePredicate);
 
        // 如果要实现 (x1) or (x2)这种情况,那么x1和x2都是一个使用and连接的Predicate,然后x1和x2使用or连接
        Predicate twoAndOrPredicate = criteriaBuilder.or(andPredicate, andEmptyPredicate);
        // 嵌套也是一样的(x1 or x2) and (x3 or x4)
        // 步骤1: x1和x2对应Predicate使用or进行连接,结果为x1x2
        // 步骤2: x3和x4对应Predicate使用or进行连接,结果为x3x4
        // 步骤3: 再将步骤一结果和步骤二结果x1x2与x3x4使用and进行连接得到x1x2Andx3x4,最终作为结果
        // (user.name = ? or username = ?) and (user.age >= 100 or user.age < 10)
        Predicate x1 = criteriaBuilder.equal(root.get("name"), "luck");
        Predicate x2 = criteriaBuilder.equal(root.get("name"), "java");
        Predicate x1x2 = criteriaBuilder.or(x1, x2);
        Predicate x3 = criteriaBuilder.ge(root.get("age"), 100);
        Predicate x4 = criteriaBuilder.lt(root.get("age"), 10);
        Predicate x3x4 = criteriaBuilder.or(x3, x4);
        Predicate x1x2Andx3x4 = criteriaBuilder.and(x1x2, x3x4);
 
        // 以上所有构建的条件都可以使用上,这个就是为了说明sql,上面构造的条件意义不大
        criteriaQuery.where(rootIsDelete, idCardDelete, orPredicate, twoAndOrPredicate, x1x2Andx3x4);
 
        // 第六步(可选): 构造分组条件并设置
        // 指定字段分组,可以有多个
        criteriaQuery.groupBy(root.get("age"));
 
        // 第七步(可选): 构造分组后条件并设置
        // 这个和上面讲的where一样
        criteriaQuery.having();
 
        // 第八步(可选): 构造排序条件并设置
        // 指定排序字段,并且设置排序方式
        criteriaQuery.orderBy(criteriaBuilder.asc(root.get("age")));
 
        // 第九步: 使用标准查询API(CriteriaQuery)构建一个可执行标准查询的Query对象
        TypedQuery<Object[]> query = entityManager.createQuery(criteriaQuery);
 
        // 第十步(可选): 设置分页参数,limit 1,10
        query.setFirstResult(1).setMaxResults(10);
 
        // 第十一步: 执行查询,获取结果
        List<Object[]> users = query.getResultList();
        for (Object[] user : users) {
            for (Object u : user) {
                System.out.print(u + ",");
            }
            System.out.println();
        }
        System.out.println(users);
原文链接:https://blog.csdn.net/JavaMyDream/article/details/137470280

Example

(QBE)是一种用户界面友好的查询技术。 它允许动态创建查询,并且不需要编写包含字段名称的查询。 实际上,按示例查询不需要使用特定的数据库的查询语言来编写查询语句。
Example api的组成
Probe: 含有对应字段的实例对象。
ExampleMatcher:ExampleMatcher携带有关如何匹配特定字段的详细信息,相当于匹配条件。
Example:由Probe和ExampleMatcher组成,用于查询。
适用场景:
使用一组静态或动态约束来查询数据存储。
频繁重构域对象,而不必担心破坏现有查询。
独立于基础数据存储API进行工作。
按示例查询也有一些限制:
不支持嵌套或分组属性约束,例如firstname =?0或(firstname =?1和lastname =?2)。
仅支持字符串的开始/包含/结束/正则表达式匹配,以及其他属性类型的完全匹配。

同Specification类似,Example的使用需要Repository接口继承QueryByExampleExecutor

public interface QueryByExampleExecutor<T> {

  <S extends T> S findOne(Example<S> example);

  <S extends T> Iterable<S> findAll(Example<S> example);

  // … more functionality omitted.
}


Person person = new Person();                         
person.setFirstname("Dave");                          
Example<Person> example = Example.of(person);       

扩展:Example Matchers

对字段查询细粒度的控制是依靠Matcher来实现的。

如下的案例:

  • 创建对象
  • 对象赋值
  • 创建ExampleMatcher
  • 忽略 lastname属性路径
  • 忽略 lastname属性路径并包含空值
  • 忽略 lastname属性路径并包含空值并进行后缀字符串匹配
  • 创建Example,配置对象与ExampleMatcher
Person person = new Person();                          
person.setFirstname("Dave");                           

ExampleMatcher matcher = ExampleMatcher.matching()     
  .withIgnorePaths("lastname")                         
  .withIncludeNullValues()                             
  .withStringMatcherEnding();                          

Example<Person> example = Example.of(person, matcher);

在这里插入图片描述

查询方法优缺点

QBC(query by critieria)QBE(Example)
优点Specification通常用于复杂查询,动态去组装查询条件。构建复杂的查询条件时,Specification实现中的代码量可能会相对较大,增加了代码的复杂性。适合简单的查询需求,通过创建一个实体类的实例并设置其属性来指定查询条件。无需编写复杂的查询语句或查询构建逻辑,降低了查询构建的复杂性。
缺点不能使用聚合灵活性有限:Example主要支持基于属性的精确匹配和模糊匹配(如LIKE操作),对于复杂的查询条件(如嵌套查询、分组、排序等)支持有限。
可重用性差:很难将查询逻辑封装并重用。每次需要执行类似查询时,都需要重新创建实体类实例并设置属性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值