续上文
1.4、定义方法
存储库代理有两种方法可以从方法名称派生特定于存储的查询。它可以直接从方法名称派生查询,或者使用手动定义的查询。可用选项取决于实际store。但是,必须有一个策略来决定创建什么样的实际查询。我们来看看可用的选项。
1.4.1、查询策略
以下策略可用于存储库基础结构来解析查询。在配置XML配置的情况下,您可以通过query-lookup-strategy属性在命名空间配置策略,或者在Java配置的情况下通过Enable $ {store}存储库注释的queryLookupStrategy属性来配置策略。某些策略可能不支持特定的数据存储。
CREATE:尝试从查询方法名称构造特定于store的查询。一般的方法是从方法名称中移除一组已知的前缀,并解析该方法的其余部分。详细看1.4.2
USE_DECLARED_QUERY会尝试查找已声明的查询,并在发现异常时会抛出异常。查询可以通过某处的注释或其他方式声明。查阅特定Store的文档以查找该Store的可用选项。如果存储库基础结构在引导时未找到该方法的已声明查询,则会失败。
CREATE_IF_NOT_FOUND(默认)结合了CREATE和USE_DECLARED_QUERY。它首先查找已声明的查询,如果未找到已声明的查询,则会创建一个基于自定义方法名称的查询。这是默认的查找策略,因此如果您没有明确配置任何内容,将会使用它。它允许通过方法名称进行快速查询定义,还可以根据需要引入已声明的查询来自定义这些查询。
1.4.2、查询创建
构建到Spring Data存储库基础结构中的查询构建器机制对构建存储库实体上的约束查询非常有用。该机制剥离前缀find... By,read ... By,query ... By,count ... By,get... By方法并开始解析其余部分。引入子句可以包含更多表达式,例如在要创建的查询上设置不同的标志。但是,第一个By作为分隔符来指示实际标准的开始。在非常基本的层次上,您可以定义实体属性的条件,并将它们与And和Or连接起来。
示例、从方法名称创建查询
interface PersonRepository extends Repository<User, Long> { List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname); // Enables the distinct flag for the query List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname); List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname); // Enabling ignoring case for an individual property List<Person> findByLastnameIgnoreCase(String lastname); // Enabling ignoring case for all suitable properties List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname); // Enabling static ORDER BY for a query List<Person> findByLastnameOrderByFirstnameAsc(String lastname); List<Person> findByLastnameOrderByFirstnameDesc(String lastname); }
解析方法的实际结果取决于您为其创建查询的持久性存储。但是,有一些一般的事情需要注意。
表达式通常是属性遍历和可以连接的运算符。您可以将属性表达式与AND和OR结合使用。您还可以获得对诸如Between,LessThan,GreaterThan等运算符的支持,就像属性表达式一样。
方法解析器支持为各个属性设置IgnoreCase标志(例如,findByLastnameIgnoreCase(...))或支持忽略大小写的类型的所有属性(通常是String实例,例如findByLastnameAndFirstnameAllIgnoreCase(...))
您可以通过将OrderBy子句附加到引用属性的查询方法并提供排序方向(Asc或Desc)来应用静态排序。
1.4.3、属性表达式
属性表达式只能引用被管实体的直接属性,如上例所示。在查询创建时,您已经确保解析的属性是托管域类的属性。但是,您也可以通过遍历嵌套属性来定义约束。假设一个人有一个ZipCode的地址。在这种情况下,方法名称为
List<Person> findByAddressZipCode(ZipCode zipCode);
创建属性遍历x.address.zipCode。解析算法首先将整个部分(AddressZipCode)解释为属性,然后检查domain类是否具有该名称的属性(未包含大小)。如果算法成功,则使用该属性。如果不是这样,该算法将来自右侧的骆驼案件部分的来源拆分为头部和尾部,并尝试查找相应的属性,在我们的示例AddressZip和Code中。如果算法找到具有该头部的属性,它将采用尾部并从该处继续构建树,然后按照刚刚描述的方式分割尾部。如果第一次拆分不匹配,则算法将拆分点移到左侧(地址,ZipCode)并继续。
虽然这应该适用于大多数情况,但算法可能会选择错误的属性。假设Person类也有addressZip属性。该算法将在第一轮拆分中匹配,并且基本上选择错误的属性并最终失败(因为addressZip的类型可能没有代码属性)。
要解决这种歧义,您可以在方法名称中使用 _手动定义遍历点。所以我们的方法名称会像这样结束:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
当我们将下划线看作保留字符时,我们强烈建议遵循标准的Java命名约定(即不要在属性名称中使用下划线,而应使用骆驼大小写)。
1.4.4、特殊参数处理
要处理查询中的参数,只需定义方法参数,如上例中已经看到的那样。除此之外,基础结构还会识别某些特定类型(如Pageable和Sort),以便动态地将分页和排序应用于查询。
示例、使用Pageable,Slice和Sort in查询方法
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实例传递给查询方法,以动态地将分页添加到静态定义的查询中。一个Page知道可用的元素和页面的总数。它通过基础设施触发计数查询来计算总数。由于这可能会很昂贵,具体取决于所使用的store,因此Slice可以用作替代。Slice只知道是否有下一个Slice可用,这在遍历更大的结果集时可能就足够了。
排序选项也是通过Pageable实例处理的。如果您只需要排序,只需在您的方法中添加org.springframework.data.domain.Sort参数。正如你所看到的,简单地返回一个List也是可能的。在这种情况下,将不会创建构建实际Page实例所需的附加元数据(这又意味着额外的计数查询本来是必需的而不是被发出)而只是简单地限制查询只查找给定范围的实体。
要找出完整查询得到的页数,您必须触发额外的计数查询。默认情况下,此查询将从您实际触发的查询中派生。
1.4.5、限定条数查询
查询方法的结果可以通过关键字first或top来限制,这些关键字可以互换使用。一个可选的数字值可以被附加到top / first,以指定要返回的最大结果大小。如果该数值被忽略,则假定结果大小为1。
示例、用Top和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关键字。此外,对于将结果集限制为一个实例的查询,支持将结果包装为可选。
如果将分页或切片应用于限制查询分页(以及计算可用页面的数量),则将其应用于有限的结果中。
注意,通过Sort参数与动态排序结合限制结果,可以表达“K”最小以及“K”最大元素的查询方法。
1.4.6、流查询
查询方法的结果可以通过使用Java 8 Stream <T>作为返回类型进行递增处理。而不是简单地将查询结果包装到Stream数据存储中,而是使用特定的方法来执行流式传输。
示例、用Java 8 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可能包装底层数据存储特定资源,因此必须在使用后关闭。您可以使用close()方法手动关闭流,也可以使用Java 7 try-with-resources块。
示例、try-with-resources块关闭 Stream <T>流
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) { stream.forEach(…); }
并非所有的Spring Data模块当前都支持Stream <T>作为返回类型。
1.4.7、异步查询Async
使用Spring的异步方法执行功能可以异步执行store查询。这意味着方法将在调用时立即返回,并且实际的查询执行将发生在已提交给Spring TaskExecutor的任务中。
//java.util.concurrent.Future @Async Future<User> findByFirstname(String firstname); //Java 8 java.util.concurrent.CompletableFuture @Async CompletableFuture<User> findOneByFirstname(String firstname); //org.springframework.util.concurrent.ListenableFuture @Async ListenableFuture<User> findOneByLastname(String lastname);
1.5、创建repository实例
将为定义的存储库接口创建实例和bean定义。一种方法是使用每个支持存储库机制的Spring Data模块附带的Spring命名空间,尽管我们通常推荐使用Java-Config样式配置。
1.5.1、XML配置
每个Spring Data模块都包含一个repositories元素,它允许您简单地定义Spring为您扫描的基本包。
示例、通过XML启用Spring Data存储库
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns: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"> <repositories base-package="com.acme.repositories" /> </beans:beans>
在前面的例子中,Spring被指示扫描com.acme.repositories及其所有子包,用于扩展Repository或其子接口之一的接口。对于找到的每个接口,基础设施注册持久性技术特定的FactoryBean以创建处理调用查询方法的适当代理。每个bean都是在从接口名称派生的bean名称下注册的,因此UserRepository的接口将注册在userRepository下。base-package属性允许使用通配符,以便您可以定义扫描包的模式。
使用filters
示例、使用排除过滤器元素
<repositories base-package="com.acme.repositories"> <context:exclude-filter type="regex" expression=".*SomeRepository" /> </repositories>
这个例子排除了所有以SomeRepository结尾的接口被实例化的接口。
1.5.2、JavaConfig
可以使用特定于@ Enable $ {store} Repositories注释在JavaConfig类上触发
@Configuration @EnableJpaRepositories("com.acme.repositories") class ApplicationConfiguration { @Bean EntityManagerFactory entityManagerFactory() { // … } }
1.5.3、独立使用
您还可以使用Spring容器之外的存储库基础结构,例如在CDI环境中。你的类路径中仍然需要一些Spring库,但通常你也可以通过编程来设置库。提供存储库支持的Spring Data模块提供了一个您可以使用的持久性技术特定的RepositoryFactory,如下所示。
示例
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
1.6、Spring Data存储库的自定义实现
1.6.1、定制个人存储
首先要为定制功能定义一个片段接口和一个实现。然后让您的存储库接口从片段接口扩展。
示例、定制存储库功能的接口和实现
interface CustomizedUserRepository { void someCustomMethod(User user); }
class CustomizedUserRepositoryImpl implements CustomizedUserRepository { public void someCustomMethod(User user) { // Your custom implementation } }
要找到的类的最重要的位是与片段接口相比较的名称的Impl后缀。
实现本身不依赖于Spring Data,可以是普通的Spring bean。因此,您可以使用标准的依赖注入行为来注入对其他bean的引用
更改您的存储库接口
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository { // Declare query methods here }
让您的存储库接口扩展一个片段。这样做结合了CRUD和自定义功能,并使其可供客户使用。
Spring数据存储库通过使用构成存储库组合的片段来实现。片段是基础知识库,QueryDsl和自定义接口等功能方面及其实现。每次将接口添加到存储库接口时,都会通过添加片段来增强组合。每个Spring Data模块提供基础知识库和存储库方面的实现。
示例、实现片段
interface HumanRepository { void someHumanMethod(User user); } class HumanRepositoryImpl implements HumanRepository { public void someHumanMethod(User user) { // Your custom implementation } } interface EmployeeRepository { void someEmployeeMethod(User user); User anotherEmployeeMethod(User user); } class ContactRepositoryImpl implements ContactRepository { public void someContactMethod(User user) { // Your custom implementation } public User anotherContactMethod(User user) { // Your custom implementation } }
更改存储接口
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository { // Declare query methods here }
存储库可能由多个自定义实现组成,这些自定义实现按其声明的顺序导入。自定义实现具有比基本实现和存储库方面更高的优先级。此排序允许您覆盖基本存储库和方面方法,并在两个片段提供相同的方法签名时解决歧义。存储库片段不限于在单个存储库接口中使用。多个存储库可以使用分段界面来重复使用跨不同存储库的定制。
示例、覆盖 save(…)片段
interface CustomizedSave<T> { <S extends T> S save(S entity); } class CustomizedSaveImpl<T> implements CustomizedSave<T> { public <S extends T> S save(S entity) { // Your custom implementation } }
自定义
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> { } interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> { }
配置
如果使用命名空间配置,则存储库基础结构会尝试通过扫描我们找到存储库所在的软件包下的类来自动检测自定义实施片段。这些类需要遵循将名称空间元素的属性repository-impl-postfix追加到找到的碎片接口名称的命名约定。这个后缀默认为Impl。
示例
<repositories base-package="com.acme.repository" /> <repositories base-package="com.acme.repository" repository-impl-postfix="FooBar" />
第一个配置示例将尝试查找类com.acme.repository.CustomizedUserRepositoryImpl作为自定义存储库实现,而第二个示例将尝试查找com.acme.repository.CustomizedUserRepositoryFooBar
解决歧义
如果在不同的包中找到具有匹配类名的多个实现,则Spring Data使用这些bean名来标识要使用的正确类。鉴于上面介绍的CustomizedUserRepository的以下两个自定义实现,第一个实现将被选中。它的bean名称是customizedUserRepositoryImpl,它与分片接口的名称相匹配(CustomizedUserRepository)加上后缀Impl。
示例、
package com.acme.impl.one; class CustomizedUserRepositoryImpl implements CustomizedUserRepository { // Your custom implementation }
package com.acme.impl.two; @Component("specialCustomImpl") class CustomizedUserRepositoryImpl implements CustomizedUserRepository { // Your custom implementation }
如果使用@Component(“specialCustom”)对UserRepository接口进行注释,那么bean名称加上Impl与为com.acme.impl.two中的存储库实现定义的接口相匹配,它将被选中而不是第一个。
手工接入实现
<repositories base-package="com.acme.repository" /> <beans:bean id="userRepositoryImpl" class="…"> <!-- further configuration --> </beans:bean>
1.6.2、自定义基础存储库
自定义存储库基类
class MyRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> { private final EntityManager entityManager; MyRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) { super(entityInformation, entityManager); // Keep the EntityManager around to used from the newly introduced methods. this.entityManager = entityManager; } @Transactional public <S extends T> S save(S entity) { // implementation goes here } }
最后一步是让Spring Data基础设施知道定制的存储库基类。在JavaConfig中,这是通过使用@ Enable ... Repositories注释的repositoryBaseClass属性来实现的:
使用JavaConfig配置定制存储库基类
@Configuration @EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class) class ApplicationConfiguration { … }
或者使用XML配置自定义存储库基类
<repositories base-package="com.acme.repository" base-class="….MyRepositoryImpl" />
1.7、从聚合根发布事件
地址:查看