高级Spring Data JPA – 规范和Querydsl
在我上一篇博客文章中,我介绍了Spring Data JPA的基本功能集。在这篇文章中,我想深入研究一些更多的功能,以及它们如何帮助您进一步简化数据访问层的实现。Spring Data存储库抽象包括一个基于接口的编程模型,一些工厂类和一个Spring命名空间,可以轻松配置基础结构。典型的存储库界面如下所示:
public interface CustomerRepository extends JpaRepository<Customer, Long> {
Customer findByEmailAddress(String emailAddress);
List<Customer> findByLastname(String lastname, Sort sort);
Page<Customer> findByFirstname(String firstname, Pageable pageable);
}
第一种方法只是希望找到具有给定电子邮件地址的单个客户,第二种方法返回具有给定姓氏的所有客户并将给定Sort
的结果应用于结果,而第三种方法返回Page
客户。有关详细信息,请查看以前的博客文章。
虽然这种方法非常方便(你甚至不需要编写一行实现代码来执行查询),但它有两个缺点:首先,由于 – 这就是大型应用程序的查询方法数量可能会增加第二点 – 查询定义了一组固定的标准。为了避免这两个缺点,如果您能够提出一组可以动态组合以构建查询的原子谓词,那不是很酷吗?
如果您是JPA长期用户,您可以回答:这不是Criteria API的用途吗?是的,让我们看看使用JPA Criteria API的示例业务需求实现是什么样的。这是用例:在他们的生日那天,我们想向所有长期客户发送优惠券。我们如何检索匹配的?
我们在谓词中有两个部分:生日以及我们称之为长期客户。让我们假设后者意味着客户帐户至少在两年前创建。以下是使用JPA 2.0 Criteria API实现的方式。
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();
我们有什么在这里?我们LocalDate
为方便起见创建了一个新的,并继续使用三行样板来设置必要的JPA基础结构实例。然后我们有两行构建谓词,一行连接两者,最后一行连接实际查询。我们正在使用JPA 2.0引入的元模型类,并由Annotation Processing API生成。这段代码的主要问题是,谓词是不容易的外部化和再利用,因为你需要设置的CriteriaBuilder
,CriteriaQuery
和Root
第一。此外,代码的可读性很差,因为很难在第一眼就快速推断出代码的意图。
产品规格
为了能够定义可重用的Predicate
s,我们引入了Specification
从Eric Evans的Domain Driven Design书中引入的概念派生的接口。它将规范定义为实体的谓词,这正是我们的Specification
接口所代表的。实际上只包含一个方法:
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}
所以我们现在可以轻松使用这样的辅助类:
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));
}
};
}
}
不可否认,这不是世界上最美丽的代码,但它很好地满足了我们的初始要求:我们可以参考一组原子规范。接下来的问题是:我们将如何执行这些规范?为此,您只需JpaSpecificationExecutor
在存储库界面中进行扩展,从而“拉入”API以执行Specification
s:
public interface CustomerRepository extends JpaRepository<Customer>, JpaSpecificationExecutor {
// Your query methods here
}
客户现在可以这样做:
customerRepository.findAll(hasBirthday());
customerRepository.findAll(isLongTermCustomer());
基本的存储库实现将为您准备CriteriaQuery
,Root
并CriteriaBuilder
为您应用Predicate
由给定创建Specification
并执行查询。但我们难道不能创建简单的查询方法来实现这一目标吗?正确,但请记住我们的第二个初始要求。我们希望能够*地组合原子Specification
以创造新的原子。为此,我们有一个辅助类Specifications
,它提供了连接原子的方法and(…)
和or(…)
方法Specification
。还有一个where(…)
提供一些语法糖,使表达更具可读性。我在开头提出的用例示例如下所示:
customerRepository.findAll(where(customerHasBirthday()).and(isLongTermCustomer()));
与单独使用JPA Criteria API相比,这可以流畅地阅读,提高可读性并提供额外的灵活性。这里唯一需要注意的是,提出Specification
实现需要相当多的编码工作。
Querydsl
为了解决这种痛苦,一个名为Querydsl的开源项目提出了一种非常相似但也不同的方法。就像JPA Criteria API一样,它使用Java 6注释处理器来生成元模型对象,但产生了更加平易近人的API。该项目的另一个很酷的事情是,它不仅支持JPA,还允许查询Hibernate,JDO,Lucene,JDBC甚至普通集合。
因此,要启动并运行,请将Querydsl添加到您的相应pom.xml
配置APT插件中。
<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>
这将导致您的构建创建特殊的查询类 – QCustomer
在我们的案例中在同一个包中。
QCustomer customer = QCustomer.customer;
LocalDate today = new LocalDate();
BooleanExpression customerHasBirthday = customer.birthday.eq(today);
BooleanExpression isLongTermCustomer = customer.createdAt.lt(today.minusYears(2));
这不仅是几乎流利的英语开箱即用,BooleanExpression
s甚至可以重复使用,没有进一步包装,这让我们摆脱额外的(并有点难看实现)Specification
包装。另外一个好处是,您可以在分配右侧的每个点处完成IDE代码,因此customer. + CTRL + SPACE
将列出所有属性。customer.birthday. + CTRL + SPACE
会列出所有可用的关键字等等。要执行Querydsl谓词,只需让您的存储库扩展QueryDslPredicateExecutor
:
public interface CustomerRepository extends JpaRepository<Customer>, QueryDslPredicateExecutor {
// Your query methods here
}
客户可以简单地做:
BooleanExpression customerHasBirthday = customer.birthday.eq(today);
BooleanExpression isLongTermCustomer = customer.createdAt.lt(today.minusYears(2));
customerRepository.findAll(customerHasBirthday.and(isLongTermCustomer));
概要
Spring Data JPA存储库抽象允许通过包装到Specification对象中的JPA Criteria API谓词或通过Querydsl谓词来执行谓词。要启用此功能,只需让您的存储库扩展JpaSpecificationExecutor或QueryDslPredicateExecutor(如果您愿意,甚至可以并排使用)。请注意,如果您决定采用Querydsl方法,则需要在类中使用Querydsl JAR。
还有一件事
关于Querydsl方法的一个更酷的事情是,它不仅可用于我们的JPA存储库,还可用于我们的MongoDB支持。该功能已包含在刚刚发布的Spring Data MongoDB的M2版本中。除此之外,CloudFoundry平台还支持Spring Data的Mongo和JPA模块。有关Spring Data和CloudFoundry的入门信息,请参阅cloudfoundry-samples wiki。
上一篇: Spring Boot添加AOP日志
下一篇: B.1004 成绩排名
推荐阅读
-
Spring-Data-JPA整合MySQL和配置的方法
-
【Spring Data JPA自学笔记三】Spring Data JPA的基础和高级查询方法
-
在Spring Data JPA中引入Querydsl的实现方式
-
spring-data-jpa原理探秘(2)-RepositoryQuery的用途和分类
-
通用mapper和mybatis-spring的关系以及与之相似的spring-data-jpa
-
配置和使用Spring Data JPA
-
jpa和spring-data-jpa
-
高级Spring Data JPA – 规范和Querydsl
-
【JPA】Spring Data JPA 实现分页和条件查询
-
Spring Data JPA注解@DynamicInsert和@DynamicUpdate