4.4定义查询方法
仓库代理有两种通过函数名的方式去获得stored-specific的查询方法。它可以通过直接使用定义好的方法名或者通过自己手动定义的方法名来调用查询。可提供的选项依赖于实际场景。然而,以下策略是在使用过程中需要考虑的。
4.4.1查询查找的策略
以下策略是供仓库的架构去解决这种查询。你可以通过在xml文件里配置query-lookup-strategy属性,或者可以在java的配置文件里通过${}赋值给QueryLookupSTrategy属性。实际上这些策略并不支持有些数据存储。
CREATE试图通过方法名称去构建一个stored-specific的查询方法。通用的方式是把大家所熟悉的前缀去掉,解析剩下部分的方法名。更多关于创建查询的内容请查看链接。
USE_DECLARED_QUERY 尝试去查找一个声明的方法,如果木有找到,则会抛出没有找到方法的异常。可以通过在某处定义方法或者声明来使用。可以通过翻阅存储文档去找到合适的存储选项。如果仓库的架构在程序引导时间内没有找到声明的方法,查找失败。
CREATE_IF_NOT_FOUND(默认) 包含了CREATE和USE_DECLARED_QUERY。它会先查找声明的查找,如果没有找到声明的查找,它将会创建一个定制的基于名字的查询方法。这是默认的查找策略并且将会被使用当你没有明确的配置方法的时候。它允许通过名称快速的定义查询,也可以有需要的通过引入声明的查询方法来自定义这些查询
4.4.2创建查询
查询构造器构建Spring Data仓库架构的机制作用于在仓库实体下构建强约束。这种机制会拆解方法里包含的find…BY。。。,然后会解析剩余的部分。引入的选项包含更多的表达式,类似于一个用于在被创建的查询里设置明显的标志的Distinct。然而,第一个出现的By作为一个分隔符去表明真实标准的开始。基础阶段,你可以通过定义实体的条件并使用And和Or把条件联合起来。
Example 11. 从方法名中创建查询
public 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…等的操作符的支持。支持的操作符因数据库而异,所以请查阅合适的章节。 方法解析器支持为个人特性设置一个IgnoreCase标志(类似于。。。),或者为一种支持忽略特性的类型的所有的属性(通常是String类型,类型于。。。)。忽略特性是否被支持取决于不同的存储。所以请查阅合适的章节。 你可以通过添加一个OrderBy条件到涉及到属性和提供了分类方向(Asc或者Desc)方法查询里。如何创建一个可以支持动态排序的查询方法,请查看。。。
4.4.3属性表达式
属性表达式仅仅只能应用于托管属性上,先前的例子中有说明。你应该在创建查询期间确认你所解析的属性是托管域的类所包含的字段。无论如何,你也能通过串联多个嵌套的属性来定义约束。例如一个Person拥有一个Address,Address也对应着一个ZipCode。在这种情况下一个方法名类似于
List<Person> findByAddressZipCode(ZipCode zipCode); 会创建属性串联x.address.ZipCode。这分解算法通过解析整一部分的(AddressZipCode)作为属性并且用属性名去查找映射类中的属性(以小写模式)。如果匹配将会使用该属性。不然,这个算法会按照驼峰法来分割它,把该属性分割为头部分和尾部分,在我们的例子里会分割成AddressZip和Code属性。若没有匹配的话将会通过建树的方式left(AddressZip,Cod),并继续在其左子树里查询。 尽管大多数情况,这将会得到解决。但这依然有可能会选择错误的属性。假设Person类里面存在addressZip属性。这个算法会首先匹配已经存在的属性,这就导致了查询失败。(因为addressZip属性可能没有Code属性)。 要解决这个问题,建议使用_去手动定义分割点。
List<Person> findByAddress_ZipCode(ZipCode zipCode);
由于我们认为下划线是我们的保留符号。我们强烈建议去跟随java的命名规则(使用驼峰法而不是下划线)。
4.4.4特殊参数处理
你只需要按照上文说的简单的定义你的方法参数,你就能在查询里处理你的参数了。此外,这个架构能够识别特定的类型,像Pageable和Sort,并应用于分页和查询时的动态排序。
Example 12. 在方法里使用分页,切片和排序 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参数知道元素的总数和页数。它是通过架构触发一个计算查询的方法去计算总数目。因为这个将会大大的依赖于存储使用的情况。Slice(切片)可以被用来作为return的代替。当处理一个巨大的结果集的时候,一个Slice只知道是否有下一个可以满足的Slice可用。
排序选项也通过Pageable实例处理。 如果只需要排序,只需向您的方法添加一个org.springframework.data.domain.Sort参数。 你也可以看到,简单地返回一个List也是可能的。 在这种情况下,将不会创建构建实际页面实例所需的附加元数据(这反过来意味着附加计数查询将不会发生),而是简单地在给定的实体范围内限制查询。
要找出所有通过查询得到的页数,你必须触发一个额外的计数方法来实现。默认情况下,这个查询是由你实际触发的方法里派生出来的。
4.4.5限制查询集
查询方法的结果可以通过关键字first或top来限制,它们可以互换使用。 通过把数字追加到top / first,来指定要返回的最大结果。 如果省略该数字,则假定结果大小为1。
Example 13. 使用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关键字。此外,对于将结果集限制为一个实例的查询,支持将结果包装为Optional。
如果将分页或切片应用于限制查询分页(以及可用页面的计算),那么它也会会被应用于限制结果集上。
4.4.6流查询集
通过使用Java 8 Stream <T>来递增地处理查询方法的结果作为返回类型。这不是简单地将查询结果包装成流数据的存储,而是使用特定方法来处理流传输。
Example 14. 使用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); 流可能会封装基础数据来存储特定资源,因此必须在使用后关闭。您可以使用close()方法或使用Java 7 try-with-resources块手动关闭流。
Example 15. 在try-with-resources块使用Stream<T>作为返回结果
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) { stream.forEach(…); }
不是所有的Spring Data模块支持Stream<T>作为返回结果。
4.4.7异步查询结果
通过使用Spring的异步方法功能可以异步地执行存储库查询。这意味着该方法将在调用时立即返回,并且实际的查询过程将发生在已经提交到Spring任务执行器的任务里。
@Async Future<User> findByFirstname(String firstname); @Async CompletableFuture<User> findOneByFirstname(String firstname); @Async ListenableFuture<User> findOneByLastname(String lastname);
- 使用 java.util.concurrent.Future 作为返回类型。
- 使用 Java 8 java.util.concurrent.CompletableFuture 作为返回类型。
- 使用 org.springframework.util.concurrent.ListenableFuture 作为返回类型。
4.5创建存储库实例
在本节中,您为定义的存储库接口创建实例和bean定义。一种方法是使用每个支持存储库机制的Spring Data模块附带的Spring命名空间,尽管我们通常建议使用Java-Config样式配置。
4.5.1XML配置
每个Spring Data模块都包含一个repository元素,它允许您简单地定义一个被Spring扫描的基础包。
Example 16. 通过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属性允许使用通配符,所以你可以自定义扫描包的模式。
使用过滤器
默认情况下,架构会在配置的基础包下挑选所有继承了持久性的特定的Repository子接口的每个接口,并为其创建一个bean实例。但是,您可能希望更精细的控制哪些接口bean实例获取创建。为此,您需要在<repositories />中使用<include-filter />和<exclude-filter />元素。语义完全等同于Spring的上下文命名空间中的元素。 有关详细信息,请参阅有关这些元素的Spring参考文档。
例如,你可以使用以下的配置从实例里面去排除特定的接口,来构建仓库。
Example 17. 使用过滤器元素 <repositories base-package="com.acme.repositories"> <context:exclude-filter type="regex" expression=".*SomeRepository" /> </repositories>
这个例子过滤了所有被实例化后以SomeRepository结尾的接口。
4.5.2Java配置
还可以通过JavaConfig类上的存储库里特有的@ Enable $ {store} Repositories声明来触发存储库架构。有关Spring容器的基于Java的配置的介绍,请参阅参考文档。
启用Spring Data存储库的示例配置如下所示。
Example 18. 基于仓库配置的声明样例 @Configuration @EnableJpaRepositories("com.acme.repositories") class ApplicationConfiguration {
@Bean public EntityManagerFactory entityManagerFactory() { // … } } 该示例使用JPA特定的注释,您可以根据实际使用的存储模块更改它。 这同样适用于EntityManagerFactory bean的定义。 请参阅涵盖商店特定配置的部分。
4.5.3独立使用
您还可以使用Spring容器之外的存储库基础结构,例如 在CDI环境中。 你仍然需要在classpath中有一些Spring类库包,但是通常你也可以用编程方式设置存储库。提供存储库支持的Spring Data模块提供了一个持久性仓库特有的RepositoryFactory的仓库,您可以使用如下。
Example 19. 仓库工厂的独立使用 RepositoryFactorySupport factory = … // Instantiate factory here UserRepository repository = factory.getRepository(UserRepository.class);