前言
参考文档
1. 依赖
由于Spring Data modules不同的开发时间,所以它们大多数都有不同的主版本号和小版本号。想要找到兼容版本的最佳方式是依赖Spring Data Release Train BOM,已经提供了兼容版本定义。在Maven项目中,你可以在POM的 <dependencyManagement />部分声明该依赖:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-releasetrain</artifactId>
<version>${release-train}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
Ingalls-SR6
。 该train的名字是按照字母顺序升序的,当前可用的版本罗列在这里 here。版本的名字遵循这种格式: ${name}-${release}
,其中release可能是下列之一:-
BUILD-SNAPSHOT
- current snapshots -
M1
,M2
etc. - milestones -
RC1
,RC2
etc. - release candidates -
RELEASE
- GA release -
SR1
,SR2
etc. - service releases
你可以在我们的 Spring Data examples repository 找到实际使用的例子。如果是就地(in place)声明Spring Data modules,你可能喜欢这样:
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
</dependencies>
1.1. 使用Spring Boot 进行依赖管理
Spring Boot已经为你选择了非常新的Spring Data modules版本。如果你想要升级到更新的版本,只需要简单的配置下 property spring-data-releasetrain.version
to the train name and iteration ,配置成你想使用的。
1.2. Spring框架
当前版本的Spring Data modules要求Spring框架版本是4.3.10.RELEASE,或更高。这些模块,也可能运行在老版本上面,但是,强烈推荐使用新的版本。
2. 使用Spring Data Repositories
Spring Data repository abstraction的目标是明显地减少呆板代码(指加载驱动、创建/关闭链接之类的事情)的数量。
2.1. 核心概念
Spring Data repository abstraction的核心接口是 Repository
。它使用domain class以及domain class的id类型作为类型参数来管理(见接口泛型)。该接口主要是作为一个标记接口,来捕获使用的类型,并帮助用户来发现继承自该接口的接口。 CrudRepository
为其管理的entities提供了复杂的CRUD功能。
publicinterfaceCrudRepository<T, ID extendsSerializable>extendsRepository<T, ID>{<S extends T> S save(S entity);(1)
T findOne(ID primaryKey);(2)Iterable<T> findAll();(3)Long count();(4)voiddelete(T entity);(5)boolean exists(ID primaryKey);(6)// … more functionality omitted.}
JpaRepository
or MongoRepository
。这些接口继承自CrudRepository
,并暴露出底层持久化技术的能力,而不仅仅是泛泛的持久化技术接口(如CrudRepository )的能力。 基于CrudRepository
还有一个 PagingAndSortingRepository
abstraction,添加了分页相关的功能:
publicinterfacePagingAndSortingRepository<T, ID extendsSerializable>extendsCrudRepository<T, ID>{Iterable<T> findAll(Sort sort);Page<T> findAll(Pageable pageable);}
如果你想访问User的第二页(每页20条记录),你可以这样做:
PagingAndSortingRepository<User,Long> repository =// … get access to a beanPage<User> users = repository.findAll(newPageRequest(1,20));
除了查询方法,还有count和delete查询的衍生:
publicinterfaceUserRepositoryextendsCrudRepository<User,Long>{Long countByLastname(String lastname);}
publicinterfaceUserRepositoryextendsCrudRepository<User,Long>{Long deleteByLastname(String lastname);List<User> removeByLastname(String lastname);}
2.2. Query methods 查询方法
标准的CRUD functionality repositories,通常会查询底层数据存储。使用Spring Data,只需要以下四个步骤:
-
声明一个接口,继承Repository 接口或其子接口,填上entity类型和id类型(主键类型)。
interfacePersonRepositoryextendsRepository<Person,Long>{…}
-
在该接口中声明查询方法。
interfacePersonRepositoryextendsRepository<Person,Long>{List<Person> findByLastname(String lastname);}
-
设置Spring,以创建这些接口的代理实例。可以使用 JavaConfig:
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;@EnableJpaRepositoriesclassConfig{}
也可以使用 XML configuration:
<?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:jpa="http://www.springframework.org/schema/data/jpa"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"><jpa:repositoriesbase-package="com.acme.repositories"/></beans>
该例子中使用了JPA namespace。如果使用其他存储的repository abstraction,你需要切换到相应的namespace,例如使用
mongodb
.来代替 jpa。还需要注意,JavaConfig没有显式地配置一个package。想要自定义需要扫描的package,可以使用@EnableJpaRepositories(或@EnableMongodbRepositories等)注解的basePackage属性。
-
获取注入的repository实例,并使用它。
publicclassSomeClient{@AutowiredprivatePersonRepository repository;publicvoid doSomething(){List<Person> persons = repository.findByLastname("Matthews");}}
下面的章节会详细解释每一步。
2.3. 定义repository接口
第一步,必须定义一个特定domain class的repository接口。该接口必须继承Repository,并填写domain class和ID type。如果你想暴露CRUD methods,可以继承 CrudRepository
,而非Repository
。
2.3.1. 微调repository定义
通常,你的repository接口会继承 Repository
、 CrudRepository
或者 PagingAndSortingRepository
。或者,如果你不想继承Spring Data interfaces的话,你可以在你的repository接口上面注解 @RepositoryDefinition
。 继承 CrudRepository
会暴露一个完整的方法集合,可以操作你的entities。如果你只想有选择地暴露一些方法,可以从 CrudRepository
中复制到你的domain repository。
这允许你定义你自己的abstraction,基于Spring Data Repositories的功能。
@NoRepositoryBeaninterfaceMyBaseRepository<T, ID extendsSerializable>extendsRepository<T, ID>{
T findOne(ID id);
T save(T entity);}interfaceUserRepositoryextendsMyBaseRepository<User,Long>{User findByEmailAddress(EmailAddress emailAddress);}
在这里的第一步,你定义了一个通用的base接口,暴露了 findOne(…)
和 save(…)
。
2.3.2. 多个Spring Data modules情况下使用Repositories
在应用中仅使用一个Spring Data module,是一件很简单的事,在定义的范围内的所有的repository接口 都被绑定到这个Spring Data module。然而,有时候,应用需要使用多个Spring Data module。在这种情况下,就要求一个repository定义能够区分出不同的持久化技术。Spring Data会进入strict repository configuration mode,因为它会在classpath中探测到多个repository factories。Strict configuration要求 repository 或 domain class的详细信息,来决定Spring Data module绑定一个repository定义:
-
如果该repository定义 继承自特定模块的repository( extends the module-specific repository),那么它就是特定Spring Data module的一个有效的候选。
-
如果该domain class 被注解了特定模块的类注解( annotated with the module-specific type annotation),那么它就是特定Spring Data module的一个有效的候选。Spring Data modules接受第三方注解 (如 JPA的
@Entity
),或者提供自己的注解如针对 Spring Data MongoDB/Spring Data Elasticsearch的@Document
注解。
interfaceMyRepositoryextendsJpaRepository<User,Long>{}@NoRepositoryBeaninterfaceMyBaseRepository<T, ID extendsSerializable>extendsJpaRepository<T, ID>{…}interfaceUserRepositoryextendsMyBaseRepository<User,Long>{…}
MyRepository
和UserRepository
继承自JpaRepository
。他们都是Spring Data JPA module的有效的候选。
interfaceAmbiguousRepositoryextendsRepository<User,Long>{…}@NoRepositoryBeaninterfaceMyBaseRepository<T, ID extendsSerializable>extendsCrudRepository<T, ID>{…}interfaceAmbiguousUserRepositoryextendsMyBaseRepository<User,Long>{…}
AmbiguousRepository
和AmbiguousUserRepository
分别继承自 Repository
和 CrudRepository
。单独使用时没有问题,但多个模块并存时,就无法区分repositories该使用哪个模块。
interfacePersonRepositoryextendsRepository<Person,Long>{…}@EntitypublicclassPerson{…}interfaceUserRepositoryextendsRepository<User,Long>{…}@DocumentpublicclassUser{…}
PersonRepository
引用了带有JPA注解 @Entity
的 Person
,因此,该repository明显属于 Spring Data JPA。 UserRepository
使用了带有 Spring Data MongoDB’s @Document
注解的 User。
interfaceJpaPersonRepositoryextendsRepository<Person,Long>{…}interfaceMongoDBPersonRepositoryextendsRepository<Person,Long>{…}@Entity@DocumentpublicclassPerson{…}
该例子,示意了一个同时带有JPA注解和Spring Data MongoDB注解的domain class。它定义了两个repositories: JpaPersonRepository
and MongoDBPersonRepository
。一个是用于JPA,另一个用于MongoDB。Spring Data不能区分respositories,这会导致未定义的行为!
Repository type details 和 identifying domain class annotations 是被用于 strict repository configuration identify repository candidates for a particular Spring Data module。 在同一个domain type上使用多个持久化技术的注解,是可能在多个持久化技术之间复用domain types,但此时 Spring Data 不再能够决定绑定到repository的唯一的module。
区分repositories的最后一种方式是scoping repository的base packages。 Base packages 定义了扫描repository接口的出发点。默认,注解驱动的配置使用该配置类所在的package。 在 XML-based configuration中,base package 是强制填写的。
@EnableJpaRepositories(basePackages ="com.acme.repositories.jpa")@EnableMongoRepositories(basePackages ="com.acme.repositories.mongo")interfaceConfiguration{}
2.4. 定义查询方法
repository代理,有两种方式来从方法名中获取一个特定存储的查询。它可以从方法名中直接获取查询,或者,可以通过使用一个自定义的查询。具体可用的选项取决于具体的存储。无论什么方式,总有一种策略来决定创建什么样的实际查询。让我们来看一下这些可用的选项。
2.4.1. Query 查找策略
下面的策略对于repository infrastructure来说,可以用于解析查询。你可以在namespace中配置该策略,通过 query-lookup-strategy
attribute (XML);也可以在Enable ${store} Repositories 注解的 queryLookupStrategy
attribute中配置(javaConfig)。一些策略可能不支持特定的数据存储。
-
CREATE
:试图从查询方法名中构建一个特定存储的查询。一般的方法是从方法中移除一个给定的大家熟知的前缀集合,并解析剩余的部分。更多关于query construction的内容,详见 Query 创建。 -
USE_DECLARED_QUERY
:试图找到一个声明了的查询,如果找不到 会抛出异常。改查下可以通过一个注解来定义,或者通过其他手段声明。查阅特定存储的文档,以找到可用的选项。 如果repository infrastructure在启动期间最终没有找到一个声明过的查询,就会失败。 -
CREATE_IF_NOT_FOUND
(默认):结合了CREATE
和USE_DECLARED_QUERY
。
2.4.2. Query 创建
Spring Data repository infrastructure中内置的query builder机制,在repository的entities上构建限制查询(constraining queries )时很有用。该机制从方法名中脱去了前缀 find…By
、 read…By
、 query…By
、 count…By
、 以及 get…By
,并解析剩余的部分。引入的语句,可以包含更多表达式,如 Distinct
。然而,第一个 By
扮演了分隔符角色,标明了实际criteria 的开始。在一个非常基础的级别上,你可以定义一些条件,并使用 And
和 Or
来连接它们。
publicinterfacePersonRepositoryextendsRepository<User,Long>{List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress,String lastname);// Enables the distinct flag for the queryList<Person> findDistinctPeopleByLastnameOrFirstname(String lastname,String firstname);List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname,String firstname);// Enabling ignoring case for an individual propertyList<Person> findByLastnameIgnoreCase(String lastname);// Enabling ignoring case for all suitable propertiesList<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname,String firstname);// Enabling static ORDER BY for a queryList<Person> findByLastnameOrderByFirstnameAsc(String lastname);List<Person> findByLastnameOrderByFirstnameDesc(String lastname);}
解析方法名的具体结果,取决于你要为哪个持久存储创建查询。不过,这里有一些通用的事情需要注意。
-
表达式通常遍历property,结合操作符(可能连接到一起)。你可以使用
AND
和OR
拼接。你也可以使用诸如Between
、LessThan
、GreaterThan
、Like
之类的操作符。不同的数据存储可能支持不同的操作符,所以,最好查阅相关参考文档。 -
方法解析器支持
IgnoreCase
flag,可以为单独的 properties设置 (例如,findByLastnameIgnoreCase(…)
),也可以为所有的 properties设置 (一般是String
instances,例如,findByLastnameAndFirstnameAllIgnoreCase(…)
)。是否支持忽略大小写,不同的存储可能不同的结果,所以,请查阅相关的参考文档。 -
你可以使用静态排序,只需要在方法名上追加一个
OrderBy
语句,需要引用一个property,还要提供排序方式 (Asc
或Desc
)。如果想创建支持动态排序的查询,见 特殊参数处理。
2.4.3. Property expressions
Property expressions 只能指向被管理entity的直接property,前面的例子已有体现。在查询创建时间,你已经确定了被解析的property是被管理的domain class的一个property。然而,你还可以为嵌套的properties定义现在(constraints)。假定 Person
有一个 Address
property,该property本身还有一个 ZipCode
property。这种情况下,下面的方法名
List<Person> findByAddressZipCode(ZipCode zipCode);
创建了 property traversal x.address.zipCode
。 该解析算法,从将整个部分 (AddressZipCode
)解释为 property 开始,检查 domain class中是否有那个名字的 property (uncapitalized)。如果该算法成功了,它就会使用那个property。如果失败了,该算法会按驼峰拆分该部分 - 从右侧开始,拆分成head和tail,然后查找head相应的property,在我们的例子中就是 AddressZip
and Code
。如果该算法 根据head找到了一个property,它会拿着tail继续构建下面的分支,按照前面的方式拆分tail。如果第一个拆分不匹配,算法会将拆分点移动到左侧 (Address
, ZipCode
),并继续。
虽然这应该已经足够应付大多数情况,该算法仍然可能选择出错误的property。假定 Person
class 也有一个 addressZip
property。该算法会在第一次拆分时就匹配,然后选择错误的property,并最终失败(因为 addressZip
的类型可能没有 code
property)。
为了解决这种模糊,你可以在方法名中使用 _
,以手动地定义遍历点。因此,我们的方法名可能是这样的:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
注意:因为我们将下划线_作为保留字符,我们强烈建议用户的开发遵守标准Java命名惯例(例如,不在property名字中使用下划线,而是用驼峰代替)。
2.4.4. 特殊参数处理
为了处理在你的查询中的参数,你可以简单地定义方法参数,如同上面所见到的那样。除此之外,the infrastructure 还可以识别特定的特殊类型,如 Pageable
and Sort
,以动态的应用分页和排序。
Page<User> findByLastname(String lastname,Pageable pageable);Slice<User> findByLastname(String lastname,Pageable pageable);List<User> findByLastname(String lastname,Sort sort);List<User> findByLastname(String lastname,Pageable pageable);
第一个方法,允许你传入一个 org.springframework.data.domain.Pageable
instance,来动态添加分页功能。一个 Page
包含元素的总数量和可用的页数。它能这样,是因为 the infrastructure 触发了一个count query,从而能够计算总数量。取决于不同的存储,这可能耗费巨大,所以可以返回Slice。一个 Slice
仅包括是否有下一个可用的 Slice
。
排序选项,也可以通过 Pageable
instance 处理。如果你只需要排序,那你只需要给你的方法添加一个 org.springframework.data.domain.Sort
参数。如你所见,直接返回一个 List
也是可以的。在这种情况下,构建 Page
instance所需的元数据就不需要了(就是说不需要额外的count query了),但只能查询给定返回的entities。
2.4.5. 限制查询结果
查询方法的结果可以使用关键字 first
或 top
来限制,这两个可以互换。一个可选的数值可以追加到 top/first ,以标明最大返回的数量。如果留空,默认是1。
Top
and First限制查询结果
User findFirstByOrderByLastnameAsc();User findTopByOrderByAgeDesc();Page<User> queryFirst10ByLastname(String lastname,Pageable pageable);Slice<User> findTop3ByLastname(String lastname,Pageable pageable);List<User> findFirst10ByLastname(String lastname,Sort sort);List<User> findTop10ByLastname(String lastname,Pageable pageable);
限制表达式,也支持关键字 Distinct
。另外,也支持 Optional
。
If pagination or slicing is applied to a limiting query pagination (and the calculation of the number of pages available) then it is applied within the limited result. -- 这句嘛意思?
Note that limiting the results in combination with dynamic sorting via a Sort
parameter allows to express query methods for the 'K' smallest as well as for the 'K' biggest elements.
2.4.6. 流式查询结果
通过使用Java 8 Stream<T>
作为返回返回类型,查询方法的结果可以被加速处理。
Stream<T>处理返回结果
@Query("select u from User u")Stream<User> findAllByCustomQueryAndStream();Stream<User> readAllByFirstnameNotNull();@Query("select u from User u")Stream<User> streamAllPaged(Pageable pageable);
Stream<T>
result try(Stream<User> stream = repository.findAllByCustomQueryAndStream()){
stream.forEach(…);}
2.4.7. 异步查询结果 (不翻译了)
Repository queries can be executed asynchronously using Spring’s asynchronous method execution capability. This means the method will return immediately upon invocation and the actual query execution will occur in a task that has been submitted to a Spring TaskExecutor.
@AsyncFuture<User> findByFirstname(String firstname);(1)@AsyncCompletableFuture<User> findOneByFirstname(String firstname);(2)@AsyncListenableFuture<User> findOneByLastname(String lastname);(3)
2.5. 创建 repository instances
在本部分,你会创建 定义过的repository接口的实例和bean 定义。一种方式是使用 Spring namespace,但我们一般都推荐使用Java-Config 形式的配置。
2.5.1. XML configuration
每个 Spring Data module 都包含一个 repositories element,允许你简单地定义一个base package -- Spring会扫描它。
<?xml version="1.0" encoding="UTF-8"?><beans:beansxmlns:beans="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/data/jpa"xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"><repositoriesbase-package="com.acme.repositories"/></beans:beans>
在上面的例子中,Spring被配置成扫描 com.acme.repositories
和其所有子包,以查找所有继承自 Repository
或其子接口的接口。对于每个找到的接口,infrastructure会注册持久化技术特定的 FactoryBean
,以创建恰当的代理,从而处理查询方法的调用。每个bean都被注册了一个bean name,默认从接口名字中获取,因此一个 UserRepository
接口,可能被注册成 userRepository
。base-package
attribute 允许通配符,所以,你还可以定义一个pattern。
使用过滤器
默认,the infrastructure会捡起每一个继承自持久化技术特定的 Repository
及其子接口的接口 -- 要在base package之下,并创建一个bean实例。但是,你或许想要更细化的控制。这时,你需要使用 <repositories /> 中的 <include-filter />
和 <exclude-filter />
elements。详见 Spring 参考文档 。
例如,想要排除特定接口,你可能会使用下面的配置:
<repositoriesbase-package="com.acme.repositories"><context:exclude-filtertype="regex"expression=".*SomeRepository"/></repositories>
该例子排除了所有以 SomeRepository
结尾的内容。
2.5.2. JavaConfig
The repository infrastructure 也可以通过在一个JavaConfig class上使用 @Enable${store}Repositories
注解来开启。
下面是一个简单的例子。
@Configuration@EnableJpaRepositories("com.acme.repositories")classApplicationConfiguration{@BeanpublicEntityManagerFactory entityManagerFactory(){// …}}
EntityManagerFactory
bean。2.5.3. 单独使用 Standalone usage
你可以在Spring container之外使用 the repository infrastructure,例如,在CDI 环境中。你仍然需要一些Spring lib,但一般,你也可以通过编码设置repositories。
RepositoryFactorySupport factory =…// Instantiate factory hereUserRepository repository = factory.getRepository(UserRepository.class);
2.6. Spring Data repositories的自定义实现
经常,需要为几个repository方法提供一个自定义的实现。Spring Data repositories允许你提供自定义的 repository 代码,并将其集成到 generic CRUD abstraction中。
2.6.1. 为某个repositories 添加自定义行为
为了丰富一个带有自定义功能的repository,你需要先定义一个接口,以及接口中自定义功能的实现。用你的repository 接口来继承这个自定义的接口。
interfaceUserRepositoryCustom{publicvoid someCustomMethod(User user);}
classUserRepositoryImplimplementsUserRepositoryCustom{publicvoid someCustomMethod(User user){// Your custom implementation}}
Impl
。interfaceUserRepositoryextendsCrudRepository<User,Long>,UserRepositoryCustom{// Declare query methods here}
让你的标准的 repository interface继承这个自定义的接口。
Configuration 配置
如果你使用namespace 配置, the repository infrastructure会试着自动探测自定义的实现,通过扫描base package中的classes。这些classes,需要遵守命名惯例,即在namespace element的 attribute repository-impl-postfix
中设置的。该后缀默认是 Impl
。
<repositoriesbase-package="com.acme.repository"/><repositoriesbase-package="com.acme.repository"repository-impl-postfix="FooBar"/>
第一个配置,会试着查找一个class: com.acme.repository.UserRepositoryImpl
,将其作为自定义的repository 实现;第二个配置,则会试着查找com.acme.repository.UserRepositoryFooBar
。
Manual wiring 手动注入 (略)
The approach just shown works well if your custom implementation uses annotation-based configuration and autowiring only, as it will be treated as any other Spring bean. If your custom implementation bean needs special wiring, you simply declare the bean and name it after the conventions just described. The infrastructure will then refer to the manually defined bean definition by name instead of creating one itself.
<repositoriesbase-package="com.acme.repository"/><beans:beanid="userRepositoryImpl"class="…"><!-- further configuration --></beans:bean>
2.6.2. 为所有repositories 添加自定义行为 (暂不需要,略)
The preceding approach is not feasible when you want to add a single method to all your repository interfaces. To add custom behavior to all repositories, you first add an intermediate interface to declare the shared behavior.
@NoRepositoryBeanpublicinterfaceMyRepository<T, ID extendsSerializable>extendsPagingAndSortingRepository<T, ID>{void sharedCustomMethod(ID id);}
Now your individual repository interfaces will extend this intermediate interface instead of the Repository
interface to include the functionality declared. Next, create an implementation of the intermediate interface that extends the persistence technology-specific repository base class. This class will then act as a custom base class for the repository proxies.
publicclassMyRepositoryImpl<T, ID extendsSerializable>extendsSimpleJpaRepository<T, ID>implementsMyRepository<T, ID>{privatefinalEntityManager entityManager;publicMyRepositoryImpl(JpaEntityInformation entityInformation,EntityManager entityManager){super(entityInformation, entityManager);// Keep the EntityManager around to used from the newly introduced methods.this.entityManager = entityManager;}publicvoid sharedCustomMethod(ID id){// implementation goes here}}
The class needs to have a constructor of the super class which the store-specific repository factory implementation is using. In case the repository base class has multiple constructors, override the one taking an EntityInformation plus a store specific infrastructure object (e.g. an EntityManager or a template class). |
The default behavior of the Spring <repositories />
namespace is to provide an implementation for all interfaces that fall under the base-package
. This means that if left in its current state, an implementation instance of MyRepository
will be created by Spring. This is of course not desired as it is just supposed to act as an intermediary between Repository
and the actual repository interfaces you want to define for each entity. To exclude an interface that extends Repository
from being instantiated as a repository instance, you can either annotate it with @NoRepositoryBean
(as seen above) or move it outside of the configured base-package
.
The final step is to make the Spring Data infrastructure aware of the customized repository base class. In JavaConfig this is achieved by using the repositoryBaseClass
attribute of the @Enable…Repositories
annotation:
@Configuration@EnableJpaRepositories(repositoryBaseClass =MyRepositoryImpl.class)classApplicationConfiguration{…}
A corresponding attribute is available in the XML namespace.
<repositoriesbase-package="com.acme.repository"base-class="….MyRepositoryImpl"/>
2.7. 从aggregate roots发布事件
被repositories管理的entities,被称作aggregate roots。在一个Domain-Driven Design 应用中,这些aggregate roots 一般会发布domain events。Spring Data提供了一个注解 @DomainEvents
,你可以用于一个方法上,从而使得发布事件足够简单。
classAnAggregateRoot{@DomainEvents //(1)Collection<Object> domainEvents(){// … return events you want to get published here}@AfterDomainEventsPublication //(2)void callbackMethod(){// … potentially clean up domain events list}}
@DomainEvents
,既可以返回一个事件实例,也可以返回事件的一个集合。注意,必须无参数。@AfterDomainEventsPublication
的方法。它,例如,可以用于潜在地清理事件列表。该方法,会在Spring Data repository’s save(…)
每次被调用时执行。
2.8. Spring Data extensions (没啥意思 略)
This section documents a set of Spring Data extensions that enable Spring Data usage in a variety of contexts. Currently most of the integration is targeted towards Spring MVC.
2.8.1. Querydsl Extension
Querydsl is a framework which enables the construction of statically typed SQL-like queries via its fluent API.
Several Spring Data modules offer integration with Querydsl via QueryDslPredicateExecutor
.
publicinterfaceQueryDslPredicateExecutor<T>{
T findOne(Predicate predicate); //(1)Iterable<T> findAll(Predicate predicate); //(2)long count(Predicate predicate); //(3)boolean exists(Predicate predicate); //(4)// … more functionality omitted.}
To make use of Querydsl support simply extend QueryDslPredicateExecutor
on your repository interface.
interfaceUserRepositoryextendsCrudRepository<User,Long>,QueryDslPredicateExecutor<User>{}
The above enables to write typesafe queries using Querydsl Predicate
s.
Predicate predicate = user.firstname.equalsIgnoreCase("dave").and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);
2.8.2. Web support (没啥意思 略)
This section contains the documentation for the Spring Data web support as it is implemented as of Spring Data Commons in the 1.6 range. As it the newly introduced support changes quite a lot of things we kept the documentation of the former behavior in Legacy web support. |
Spring Data modules ships with a variety of web support if the module supports the repository programming model. The web related stuff requires Spring MVC JARs on the classpath, some of them even provide integration with Spring HATEOAS [2]. In general, the integration support is enabled by using the @EnableSpringDataWebSupport
annotation in your JavaConfig configuration class.
@Configuration@EnableWebMvc@EnableSpringDataWebSupportclassWebConfiguration{}
The @EnableSpringDataWebSupport
annotation registers a few components we will discuss in a bit. It will also detect Spring HATEOAS on the classpath and register integration components for it as well if present.
Alternatively, if you are using XML configuration, register either SpringDataWebSupport
or HateoasAwareSpringDataWebSupport
as Spring beans:
<beanclass="org.springframework.data.web.config.SpringDataWebConfiguration"/><!-- If you're using Spring HATEOAS as well register this one *instead* of the former --><beanclass="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration"/>
Basic web support
The configuration setup shown above will register a few basic components:
-
A
DomainClassConverter
to enable Spring MVC to resolve instances of repository managed domain classes from request parameters or path variables. -
HandlerMethodArgumentResolver
implementations to let Spring MVC resolve Pageable and Sort instances from request parameters.
DomainClassConverter
The DomainClassConverter
allows you to use domain types in your Spring MVC controller method signatures directly, so that you don’t have to manually lookup the instances via the repository:
@Controller@RequestMapping("/users")publicclassUserController{@RequestMapping("/{id}")publicString showUserForm(@PathVariable("id")User user,Model model){
model.addAttribute("user", user);return"userForm";}}
As you can see the method receives a User instance directly and no further lookup is necessary. The instance can be resolved by letting Spring MVC convert the path variable into the id type of the domain class first and eventually access the instance through calling findOne(…)
on the repository instance registered for the domain type.
Currently the repository has to implement CrudRepository to be eligible to be discovered for conversion. |
HandlerMethodArgumentResolvers for Pageable and Sort
The configuration snippet above also registers a PageableHandlerMethodArgumentResolver
as well as an instance of SortHandlerMethodArgumentResolver
. The registration enables Pageable
and Sort
being valid controller method arguments
@Controller@RequestMapping("/users")publicclassUserController{@AutowiredUserRepository repository;@RequestMappingpublicString showUsers(Model model,Pageable pageable){
model.addAttribute("users", repository.findAll(pageable));return"users";}}
This method signature will cause Spring MVC try to derive a Pageable instance from the request parameters using the following default configuration:
|
Page you want to retrieve, 0 indexed and defaults to 0. |
|
Size of the page you want to retrieve, defaults to 20. |
|
Properties that should be sorted by in the format |
To customize this behavior extend either SpringDataWebConfiguration
or the HATEOAS-enabled equivalent and override the pageableResolver()
or sortResolver()
methods and import your customized configuration file instead of using the @Enable
-annotation.
In case you need multiple Pageable
or Sort
instances to be resolved from the request (for multiple tables, for example) you can use Spring’s @Qualifier
annotation to distinguish one from another. The request parameters then have to be prefixed with ${qualifier}_
. So for a method signature like this:
publicString showUsers(Model model,@Qualifier("foo")Pageable first,@Qualifier("bar")Pageable second){…}
you have to populate foo_page
and bar_page
etc.
The default Pageable
handed into the method is equivalent to a new PageRequest(0, 20)
but can be customized using the @PageableDefaults
annotation on the Pageable
parameter.
Hypermedia support for Pageables
Spring HATEOAS ships with a representation model class PagedResources
that allows enriching the content of a Page
instance with the necessary Page
metadata as well as links to let the clients easily navigate the pages. The conversion of a Page to a PagedResources
is done by an implementation of the Spring HATEOAS ResourceAssembler
interface, the PagedResourcesAssembler
.
@ControllerclassPersonController{@AutowiredPersonRepository repository;@RequestMapping(value ="/persons", method =RequestMethod.GET)HttpEntity<PagedResources<Person>> persons(Pageable pageable,PagedResourcesAssembler assembler){Page<Person> persons = repository.findAll(pageable);returnnewResponseEntity<>(assembler.toResources(persons),HttpStatus.OK);}}
Enabling the configuration as shown above allows the PagedResourcesAssembler
to be used as controller method argument. Calling toResources(…)
on it will cause the following:
-
The content of the
Page
will become the content of thePagedResources
instance. -
The
PagedResources
will get aPageMetadata
instance attached populated with information form thePage
and the underlyingPageRequest
. -
The
PagedResources
getsprev
andnext
links attached depending on the page’s state. The links will point to the URI the method invoked is mapped to. The pagination parameters added to the method will match the setup of thePageableHandlerMethodArgumentResolver
to make sure the links can be resolved later on.
Assume we have 30 Person instances in the database. You can now trigger a request GET http://localhost:8080/persons
and you’ll see something similar to this:
{"links":[{"rel":"next","href":"http://localhost:8080/persons?page=1&size=20 }],"content":[…// 20 Person instances rendered here],"pageMetadata":{"size":20,"totalElements":30,"totalPages":2,"number":0}}
You see that the assembler produced the correct URI and also picks up the default configuration present to resolve the parameters into a Pageable
for an upcoming request. This means, if you change that configuration, the links will automatically adhere to the change. By default the assembler points to the controller method it was invoked in but that can be customized by handing in a custom Link
to be used as base to build the pagination links to overloads of the PagedResourcesAssembler.toResource(…)
method.
Querydsl web support
For those stores having QueryDSL integration it is possible to derive queries from the attributes contained in a Request
query string.
This means that given the User
object from previous samples a query string
?firstname=Dave&lastname=Matthews
can be resolved to
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
using the QuerydslPredicateArgumentResolver
.
The feature will be automatically enabled along @EnableSpringDataWebSupport when Querydsl is found on the classpath. |
Adding a @QuerydslPredicate
to the method signature will provide a ready to use Predicate
which can be executed via the QueryDslPredicateExecutor
.
Type information is typically resolved from the methods return type. Since those information does not necessarily match the domain type it might be a good idea to use the root attribute of QuerydslPredicate . |
@ControllerclassUserController{@AutowiredUserRepository repository;@RequestMapping(value ="/", method =RequestMethod.GET)String index(Model model,@QuerydslPredicate(root =User.class)Predicate predicate, //(1)Pageable pageable,@RequestParamMultiValueMap<String,String> parameters){
model.addAttribute("users", repository.findAll(predicate, pageable));return"index";}}
Predicate
for User
.The default binding is as follows:
-
Object
on simple properties aseq
. -
Object
on collection like properties ascontains
. -
Collection
on simple properties asin
.
Those bindings can be customized via the bindings
attribute of @QuerydslPredicate
or by making use of Java 8 default methods
adding the QuerydslBinderCustomizer
to the repository interface.
interfaceUserRepositoryextendsCrudRepository<User,String>,QueryDslPredicateExecutor<User>,(1)QuerydslBinderCustomizer<QUser>{(2)@Overridedefaultpublicvoid customize(QuerydslBindings bindings,QUser user){
bindings.bind(user.username).first((path, value)-> path.contains(value))(3)
bindings.bind(String.class).first((StringPath path,String value)-> path.containsIgnoreCase(value));(4)
bindings.excluding(user.password);(5)}}
1 | QueryDslPredicateExecutor provides access to specific finder methods for Predicate . |
2 | QuerydslBinderCustomizer defined on the repository interface will be automatically picked up and shortcuts @QuerydslPredicate(bindings=…) . |
3 | Define the binding for the username property to be a simple contains binding. |
4 | Define the default binding for String properties to be a case insensitive contains match. |
5 | Exclude the password property from Predicate resolution. |
2.8.3. Repository populators (没啥意思 略)
If you work with the Spring JDBC module, you probably are familiar with the support to populate a DataSource
using SQL scripts. A similar abstraction is available on the repositories level, although it does not use SQL as the data definition language because it must be store-independent. Thus the populators support XML (through Spring’s OXM abstraction) and JSON (through Jackson) to define data with which to populate the repositories.
Assume you have a file data.json
with the following content:
[{"_class":"com.acme.Person","firstname":"Dave","lastname":"Matthews"},{"_class":"com.acme.Person","firstname":"Carter","lastname":"Beauford"}]
You can easily populate your repositories by using the populator elements of the repository namespace provided in Spring Data Commons. To populate the preceding data to your PersonRepository , do the following:
<?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:repository="http://www.springframework.org/schema/data/repository"xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
http://www.springframework.org/schema/data/repository/spring-repository.xsd"><repository:jackson2-populatorlocations="classpath:data.json"/></beans>
This declaration causes the data.json
file to be read and deserialized via a Jackson ObjectMapper
.
The type to which the JSON object will be unmarshalled to will be determined by inspecting the _class
attribute of the JSON document. The infrastructure will eventually select the appropriate repository to handle the object just deserialized.
To rather use XML to define the data the repositories shall be populated with, you can use the unmarshaller-populator
element. You configure it to use one of the XML marshaller options Spring OXM provides you with. See the Spring 参考文档 for details.
<?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:repository="http://www.springframework.org/schema/data/repository"xmlns:oxm="http://www.springframework.org/schema/oxm"xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
http://www.springframework.org/schema/data/repository/spring-repository.xsd
http://www.springframework.org/schema/oxm
http://www.springframework.org/schema/oxm/spring-oxm.xsd"><repository:unmarshaller-populatorlocations="classpath:data.json"unmarshaller-ref="unmarshaller"/><oxm:jaxb2-marshallercontextPath="com.acme"/></beans>
2.8.4. Legacy web support (没啥意思 略)
Domain class web binding for Spring MVC
Given you are developing a Spring MVC web application you typically have to resolve domain class ids from URLs. By default your task is to transform that request parameter or URL part into the domain class to hand it to layers below then or execute business logic on the entities directly. This would look something like this:
@Controller@RequestMapping("/users")publicclassUserController{privatefinalUserRepository userRepository;@AutowiredpublicUserController(UserRepository userRepository){Assert.notNull(repository,"Repository must not be null!");this.userRepository = userRepository;}@RequestMapping("/{id}")publicString showUserForm(@PathVariable("id")Long id,Model model){// Do null check for idUser user = userRepository.findOne(id);// Do null check for user
model.addAttribute("user", user);return"user";}}
First you declare a repository dependency for each controller to look up the entity managed by the controller or repository respectively. Looking up the entity is boilerplate as well, as it’s always a findOne(…)
call. Fortunately Spring provides means to register custom components that allow conversion between a String
value to an arbitrary type.
PropertyEditors
For Spring versions before 3.0 simple Java PropertyEditors
had to be used. To integrate with that, Spring Data offers a DomainClassPropertyEditorRegistrar
, which looks up all Spring Data repositories registered in the ApplicationContext
and registers a custom PropertyEditor
for the managed domain class.
<beanclass="….web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"><propertyname="webBindingInitializer"><beanclass="….web.bind.support.ConfigurableWebBindingInitializer"><propertyname="propertyEditorRegistrars"><beanclass="org.springframework.data.repository.support.DomainClassPropertyEditorRegistrar"/></property></bean></property></bean>
If you have configured Spring MVC as in the preceding example, you can configure your controller as follows, which reduces a lot of the clutter and boilerplate.
@Controller@RequestMapping("/users")publicclassUserController{@RequestMapping("/{id}")publicString showUserForm(@PathVariable("id")User user,Model model){
model.addAttribute("user", user);return"userForm";}}
3. Query by Example
3.1.介绍
本章节会介绍 使用Example进行查询,并解释如何使用。
Query by Example (QBE) ,是一种用户友好的查询技术。它允许动态查询创建,不需要写包含字段名字的查询。实际上,QBE完全不要求写特定存储的查询语言。
3.2.使用
使用Example查询由三部分组成:
-
Probe: 一个domain object的具体example。
-
ExampleMatcher
:ExampleMatcher
携带了如何匹配特定的字段的详细信息。可以在多个Examples之间复用。 -
Example
: 由probe 和ExampleMatcher
构成的Example。被用于创建查询。
Query by Example 适合一些用例,但仍有其局限性:
什么时候使用
-
使用一组静态或动态限制(constraints)来查询时。
-
经常重构 domain objects,而不需要担心破坏现有查询。
-
独立于底层的数据存储API。
局限
-
不支持嵌套的/分组的 property constraints,如
firstname = ?0 or (firstname = ?1 and lastname = ?2)
-
仅支持字符串的 starts/contains/ends/regex 匹配和其他类型的精确匹配。
在开始使用 Query by Example之前,你需要有一个 domain object。先为你的repository简单地创建一个接口:
publicclassPerson{@IdprivateString id;privateString firstname;privateString lastname;privateAddress address;// … getters and setters omitted}
这是一个简单的 domain object。你可以使用它来创建一个 Example
。默认,值为null的字段会被忽略,字符串使用存储的默认匹配。可以使用Example.of() (这是一个工厂方法)创建Examples,也可以使用 ExampleMatcher
.。Example
是不可改变的。
Person person =newPerson();(1)
person.setFirstname("Dave");(2)Example<Person> example =Example.of(person);(3)
Examples are ideally be executed with repositories. To do so, let your repository interface extend QueryByExampleExecutor<T>
. Here’s an excerpt from the QueryByExampleExecutor
interface:
QueryByExampleExecutor
publicinterfaceQueryByExampleExecutor<T>{<S extends T> S findOne(Example<S> example);<S extends T>Iterable<S> findAll(Example<S> example);// … more functionality omitted.}
You can read more about Query by Example Execution below.
3.3. Example matchers
Examples are not limited to default settings. You can specify own defaults for string matching, null handling and property-specific settings using the ExampleMatcher
.
Person person =newPerson();(1)
person.setFirstname("Dave");(2)ExampleMatcher matcher =ExampleMatcher.matching()(3).withIgnorePaths("lastname")(4).withIncludeNullValues()(5).withStringMatcherEnding();(6)Example<Person> example =Example.of(person, matcher);(7)
ExampleMatcher
,期望所有的values都匹配。即便没有更多的配置,它也是可用的。ExampleMatcher
,忽略property path lastname 。ExampleMatcher
,忽略property path lastname,并包含所有的null values。ExampleMatcher
,忽略property path lastname,并包含所有的null values,并使用末尾字符串匹配。Example
,基于domain object和配置好的 ExampleMatcher
。默认,the ExampleMatcher
会期望probe里设置的所有的 values 都匹配。你还可以使用 ExampleMatcher.matchingAny()
.
你可以为单独的properties指定行为 (e.g. "firstname" and "lastname", "address.city" for nested properties)。你可以调节匹配选项和大小写敏感性。
ExampleMatcher matcher =ExampleMatcher.matching().withMatcher("firstname", endsWith()).withMatcher("lastname", startsWith().ignoreCase());}
另一种方式来配置 matcher选项,是使用 Java 8 lambdas。这种一种回调,要求实现者修改matcher。不要求返回matcher,因为配置选项是在matcher实例中保持的。
ExampleMatcher matcher =ExampleMatcher.matching().withMatcher("firstname", match -> match.endsWith()).withMatcher("firstname", match -> match.startsWith());}
使用Example
创建的查询,使用了一种融合的配置view。默认的匹配设置,可以在 ExampleMatcher
级别上设置,同时单个的设置也可以应用到特定的property paths上。设置值 ExampleMatcher
上的设置,会被property path settings继承,除非它们( property path settings)有显式地定义-- 这样有更高的优先级。
Setting | Scope |
---|---|
Null-handling |
|
String matching |
|
Ignoring properties |
Property path |
Case sensitivity |
|
Value transformation |
Property path |
4. Auditing 审计
4.1. 基础
Spring Data提供了复杂的支持,以透明地跟踪谁创建或更改了entity,以及发生的时间。为了利用这种功能,你需要为你的entity classes装备上auditing metadata -- 可以使用注解,或者通过实现特定的接口。
4.1.1. 基于注解的auditing metadata
我们提供了 @CreatedBy
、 @LastModifiedBy
来捕获创建或修改entity的用户,还提供了 @CreatedDate
和 @LastModifiedDate
来捕获发生的时间。
classCustomer{@CreatedByprivateUser user;@CreatedDateprivateDateTime createdDate;// … further properties omitted}
如你所见,可以有选择地适用注解,取决于你想捕获哪些信息。捕获时间的注解,可以用于properties of type JodaTimes DateTime
、传统的 Java Date
和 Calendar
、JDK8 date/time types,以及 long
/Long
。
4.1.2. 基于接口的auditing metadata
如果你不想使用注解来定义auditing metadata,你可以让你的domain class实现Auditable
接口。它暴露了所有auditing properties的setter methods。
还有一个便捷的基类 AbstractAuditable
可以继承,以避免需要手动实现接口方法。注意,这增加了与Spring的耦合,这是我们极力避免的。通常推荐使用注解方式,因为更少侵入,更加弹性。
4.1.3. AuditorAware
当你使用 @CreatedBy
或 @LastModifiedBy
时,the auditing infrastructure需要知道当前的 principal。我们提供了一个 AuditorAware<T>
SPI 接口,你必须实现,以告诉the infrastructure当前用户或者系统是什么。泛型 T
定义了什么类型的 properties注解了 @CreatedBy
或 @LastModifiedBy
。
这里是一个示例实现了该接口,使用Spring Security的Authentication
object:
classSpringSecurityAuditorAwareimplementsAuditorAware<User>{publicUser getCurrentAuditor(){Authentication authentication =SecurityContextHolder.getContext().getAuthentication();if(authentication ==null||!authentication.isAuthenticated()){returnnull;}return((MyUserDetails) authentication.getPrincipal()).getUser();}}
附录
附录 A: Namespace reference
The <repositories /> element
The <repositories />
element triggers the setup of the Spring Data repository infrastructure. The most important attribute is base-package
which defines the package to scan for Spring Data repository interfaces.[3]
Name | Description |
---|---|
|
Defines the package to be used to be scanned for repository interfaces extending *Repository (actual interface is determined by specific Spring Data module) in auto detection mode. All packages below the configured package will be scanned, too. Wildcards are allowed. |
|
Defines the postfix to autodetect custom repository implementations. Classes whose names end with the configured postfix will be considered as candidates. Defaults to |
|
Determines the strategy to be used to create finder queries. See Query lookup strategies for details. Defaults to |
|
Defines the location to look for a Properties file containing externally defined queries. |
|
Controls whether nested repository interface definitions should be considered. Defaults to |
附录 B: Populators namespace reference
The <populator /> element
The <populator />
element allows to populate the a data store via the Spring Data repository infrastructure.[4]
Name | Description |
---|---|
|
Where to find the files to read the objects from the repository shall be populated with. |
附录 C: Repository查询关键字
支持的查询关键字
The following table lists the keywords generally supported by the Spring Data repository query derivation mechanism. However, consult the store-specific documentation for the exact list of supported keywords, because some listed here might not be supported in a particular store.
Logical keyword | Keyword expressions |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
附录 D: Repository查询的返回类型
支持的查询的返回类型
The following table lists the return types generally supported by Spring Data repositories. However, consult the store-specific documentation for the exact list of supported return types, because some listed here might not be supported in a particular store.
Geospatial types like (GeoResult , GeoResults , GeoPage ) are only available for data stores that support geospatial queries. |
Return type | Description |
---|---|
|
Denotes no return value. |
Primitives |
Java primitives. |
Wrapper types |
Java wrapper types. |
|
An unique entity. Expects the query method to return one result at most. In case no result is found |
|
An |
|
A |
|
A |
|
A Java 8 or Guava |
|
An either Scala or JavaSlang |
|
A Java 8 |
|
A |
|
A Java 8 |
|
A |
|
A sized chunk of data with information whether there is more data available. Requires a |
|
A |
|
A result entry with additional information, e.g. distance to a reference location. |
|
A list of |
|
A |