概述
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操作),对于复杂的查询条件(如嵌套查询、分组、排序等)支持有限。 可重用性差:很难将查询逻辑封装并重用。每次需要执行类似查询时,都需要重新创建实体类实例并设置属性。 |
4052

被折叠的 条评论
为什么被折叠?



