Introduction
在基本的数据查询实例中,可以通过实现CrudRepository接口来实现针对一个的查询或多个字段的组合查询,但这只是对于条件比较简单的情况下,如果条件比较复杂,那么一个方法的名字就会显的很长,那么就可以换一种方式来实现数据查询,比如下面即将提到的Criteria API, Specification, Query dsl.
Criteria API
CriteriaQuery
一个典型的查询代码如下:
LocalDate today = new LocalDate();
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Customer> query = builder.createQuery(Customer.class);
Root<Customer> root = query.from(Customer.class);
Predicate hasBirthday = builder.equal(root.get(Customer_.birthday), today);
Predicate isLongTermCustomer = builder.lessThan(root.get(Customer_.createdAt), today.minusYears(2);
query.where(builder.and(hasBirthday, isLongTermCustomer));
em.createQuery(query.select(root)).getResultList();
主要步骤如下:
- 创建查询构造器
CriteriaBuilder
- 实例化一个条件查询
- 创建一个查询的根元素
- 创建一个或多个查询的条件预测
- 在
entityManager
上执行查询
CriteriaUpdate/CriteriaDelete
CriteriaQuery
是jpa 2.0引入的接口,而在jpa 2.1中还引入了CriteriaUpdate
和CriteriaDelete
接口来实现对数据的修改和删除操作。
CriteriaUpdate
的实现和CriteriaQuery
类似,其实现如下:
CriteriaBuilder builder = entityManager
.getCriteriaBuilder();
CriteriaUpdate<T> update = builder
.createCriteriaUpdate(postModerateClass);
Root<T> root = update.from(postModerateClass);
Expression<Boolean> filterPredicate = builder
.like(
builder.lower(root.get("message")),
"%spam%"
);
if(Post.class.isAssignableFrom(postModerateClass)) {
filterPredicate = builder.or(
filterPredicate, builder
.like(
builder.lower(root.get("title")),
"%spam%"
)
);
}
update
.set(root.get("status"), PostStatus.SPAM)
.set(root.get("updatedOn"), new Date())
.where(filterPredicate);
return entityManager
.createQuery(update)
.executeUpdate()
当然如果觉的写那么多模板代码比较麻烦,也可以直接通过EntityManager
的createQuery
接口手写sql, 也是可以的。也有其他的方法,如下面的Specification
接口。
Specification接口
Specification
接口实现了可重用的预测(Predicate), 一个Specification
接口就是一个查询条件,可以通过创建多个Specification
接口实现复杂的条件查询,其接口如下:
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}
Root
, CriteriaQuery
, CriteriaBuilder
和Criteria
中的一样,下面是一个示例:
public CustomerSpecifications {
public static Specification<Customer> customerHasBirthday() {
return new Specification<Customer> {
public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
return cb.equal(root.get(Customer_.birthday), today);
}
};
}
public static Specification<Customer> isLongTermCustomer() {
return new Specification<Customer> {
public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2));
}
};
}
}
为了让repository可以执行这个查询,需要repository接口实现JpaSpecificationExecutor
接口。查询实例如下:
customerRepository.findAll(hasBirthday());
或者实现组合查询(jpa中提供了where, and, or 等帮助方法来简化操作):
customerRepository.findAll(where(customerHasBirthday()).and(isLongTermCustomer()));
Querydsl
开源项目Querydsl也提供了简化模板代码的实现, 你需要pom.xml
中添加querydsl的包,然后添加插件以实现在每个源码包下自动创建查询类比如QCustomer
。
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>maven-apt-plugin</artifactId>
<version>1.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources</outputDirectory>
<processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
查询代码如下:
BooleanExpression customerHasBirthday = customer.birthday.eq(today);
BooleanExpression isLongTermCustomer = customer.createdAt.lt(today.minusYears(2));
customerRepository.findAll(customerHasBirthday.and(isLongTermCustomer));
其中BooleanExpression和Specification类似,当然,repository需要实现相应的接口
public interface CustomerRepository extends JpaRepository<Customer>, QueryDslPredicateExecutor {
}
https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
https://vladmihalcea.com/jpa-criteria-api-bulk-update-delete/