原标题:Spring认证中国教育管理中心-Spring Data Couchbase教程四(Spring中国教育管理中心)
4.3.定义存储库接口
要定义存储库接口,您首先需要定义特定于域类的存储库接口。接口必须扩展Repository并输入到域类和 ID 类型。如果要公开该域类型的 CRUD 方法,请扩展CrudRepository而不是Repository.
4.3.1微调存储库定义
通常情况下,你的资料库接口扩展Repository,CrudRepository或
PagingAndSortingRepository。或者,如果您不想扩展 Spring Data 接口,也可以使用@RepositoryDefinition. 扩展CrudRepository公开了一整套操作实体的方法。如果您希望对公开的方法有选择性,请将要公开的方法复制CrudRepository到您的域存储库中。
这样做可以让您在提供的 Spring Data Repositories 功能之上定义自己的抽象。
下面的示例示出了如何以选择性地露出CRUD方法(findById和save,在这种情况下):
示例 28. 选择性地公开 CRUD 方法
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends Repository<T, ID> {
Optional<T> findById(ID id);
<S extends T> S save(S entity);
}
interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(EmailAddress emailAddress);
}
在前面的例子,你定义为所有站点库一个共同的基础界面和暴露findById(…),以及save(…)。这些方法被发送到基础信息库实现你所选择的由Spring提供的数据(例如,如果使用JPA商店,实现是SimpleJpaRepository),因为它们与CrudRepository. 所以UserRepository现在可以保存用户,通过 ID 查找单个用户,并触发查询以Users通过电子邮件地址查找。
中间存储库接口用@NoRepositoryBean. 确保将该注释添加到 Spring Data 不应在运行时为其创建实例的所有存储库接口。
4.3.2.将存储库与多个 Spring 数据模块一起使用
在应用程序中使用唯一的 Spring Data 模块会使事情变得简单,因为定义范围内的所有存储库接口都绑定到 Spring Data 模块。有时,应用程序需要使用多个 Spring Data 模块。在这种情况下,存储库定义必须区分持久性技术。当检测到类路径上有多个存储库工厂时,Spring Data 进入严格的存储库配置模式。严格配置使用存储库或域类的详细信息来决定存储库定义的 Spring Data 模块绑定:
- 如果存储库定义扩展了特定于模块的存储库,则它是特定 Spring Data 模块的有效候选者。
- 如果域类使用特定于模块的类型注释进行注释,则它是特定 Spring Data 模块的有效候选者。Spring Data 模块接受第三方注解(例如 JPA's @Entity)或提供自己的注解(例如@DocumentSpring Data MongoDB 和 Spring Data Elasticsearch)。
以下示例显示了使用特定于模块的接口(在本例中为 JPA)的存储库:
示例 29. 使用模块特定接口的存储库定义
interface MyRepository extends JpaRepository<User, Long> { }
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends JpaRepository<T, ID> { … }
interface UserRepository extends MyBaseRepository<User, Long> { … }
MyRepository并在它们的类型层次结构中UserRepository扩展JpaRepository。它们是 Spring Data JPA 模块的有效候选者。
以下示例显示了使用通用接口的存储库:
示例 30. 使用通用接口的存储库定义
interface AmbiguousRepository extends Repository<User, Long> { … }
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends CrudRepository<T, ID> { … }
interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }
AmbiguousRepository和AmbiguousUserRepository仅延伸Repository,并CrudRepository在他们的类型层次。虽然在使用唯一的 Spring Data 模块时这很好,但多个模块无法区分这些存储库应该绑定到哪个特定的 Spring Data。
以下示例显示了一个使用带注释的域类的存储库:
示例 31. 使用带注释的域类的存储库定义
interface PersonRepository extends Repository<Person, Long> { … }
@Entity
class Person { … }
interface UserRepository extends Repository<User, Long> { … }
@Document
class User { … }
PersonRepositoryreferences Person,使用 JPA@Entity注释进行注释,因此这个存储库显然属于 Spring Data JPA。UserRepositoryreferences User,使用 Spring Data MongoDB 的@Document注解进行注解。
以下错误示例显示了一个使用具有混合注释的域类的存储库:
示例 32. 使用具有混合注释的域类的存储库定义
interface JpaPersonRepository extends Repository<Person, Long> { … }
interface MongoDBPersonRepository extends Repository<Person, Long> { … }
@Entity
@Document
class Person { … }
此示例显示了使用 JPA 和 Spring Data MongoDB 注释的域类。它定义了两个存储库,JpaPersonRepository并且MongoDBPersonRepository. 一个用于 JPA,另一个用于 MongoDB。Spring Data 不再能够区分存储库,这会导致未定义的行为。
存储库类型详细信息和区分域类注释用于严格的存储库配置,以识别特定 Spring Data 模块的存储库候选者。在同一域类型上使用多个持久性技术特定的注释是可能的,并且可以跨多个持久性技术重用域类型。但是,Spring Data 无法再确定绑定存储库的唯一模块。
区分存储库的最后一种方法是确定存储库基础包的范围。基本包定义了扫描存储库接口定义的起点,这意味着将存储库定义放在适当的包中。默认情况下,注解驱动配置使用配置类的包。基于 XML 的配置中的基本包是必需的。
以下示例显示了基本包的注释驱动配置:
示例 33. 基本包的注释驱动配置
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration { … }
4.4.定义查询方法
存储库代理有两种方法可以从方法名称派生特定于存储的查询:
- 通过直接从方法名称派生查询。
- 通过使用手动定义的查询。
可用选项取决于实际商店。但是,必须有一种策略来决定创建什么实际查询。下一节将介绍可用的选项。
4.4.1。查询查找策略
以下策略可用于存储库基础架构来解决查询。使用 XML 配置,您可以通过query-lookup-strategy属性在命名空间配置策略。对于 Java 配置,您可以使用注解的queryLookupStrategy属性Enable${store}Repositories。特定数据存储可能不支持某些策略。
- CREATE尝试从查询方法名称构造特定于存储的查询。一般的方法是从方法名称中删除一组给定的已知前缀并解析方法的其余部分。您可以在“查询创建”中阅读有关查询构造的更多信息。
- USE_DECLARED_QUERY尝试查找已声明的查询,如果找不到则抛出异常。查询可以由某处的注释定义或通过其他方式声明。请参阅特定商店的文档以查找该商店的可用选项。如果存储库基础结构在引导时没有找到该方法的声明查询,它就会失败。
- CREATE_IF_NOT_FOUND(默认)结合CREATE和USE_DECLARED_QUERY。它首先查找已声明的查询,如果未找到已声明的查询,则创建一个基于自定义方法名称的查询。这是默认查找策略,因此,如果您未明确配置任何内容,则使用此策略。它允许通过方法名称快速定义查询,还可以通过根据需要引入声明的查询来自定义调整这些查询。
4.4.2.查询创建
Spring Data 存储库基础结构中内置的查询构建器机制对于在存储库的实体上构建约束查询很有用。
以下示例显示了如何创建多个查询:
示例 34. 从方法名称创建查询
interface PersonRepository extends Repository<Person, 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);
}
解析查询方法名称分为主语和谓语。第一部分 ( find…By, exists…By) 定义查询的主题,第二部分构成谓词。引言从句(主语)可以包含进一步的表达。find(或其他介绍关键字)和之间的任何文本都By被认为是描述性的,除非使用结果限制关键字之一,例如Distinct在要创建的查询上设置不同的标志或Top/First来限制查询结果。
附录包含查询方法主题关键字和查询方法谓词关键字的完整列表,包括排序和字母大小写修饰符。但是,第一个By用作分隔符以指示实际条件谓词的开始。在非常基本的级别上,您可以在实体属性上定义条件并将它们与And和连接起来Or。
解析方法的实际结果取决于您为其创建查询的持久性存储。但是,有一些一般的事情需要注意:
- 表达式通常是结合了可以连接的运算符的属性遍历。您可以将属性表达式与AND和结合使用OR。您还可以得到这样的运营商为支撑Between,LessThan,GreaterThan,和Like该属性的表达式。支持的运算符可能因数据存储而异,因此请参阅参考文档的相应部分。
- 方法解析器支持IgnoreCase为单个属性(例如,findByLastnameIgnoreCase(…))或支持忽略大小写的类型的所有属性(通常是String实例 - 例如,findByLastnameAndFirstnameAllIgnoreCase(…))设置标志。是否支持忽略大小写可能因商店而异,因此请参阅参考文档中的相关部分以了解商店特定的查询方法。
- 您可以通过将OrderBy子句附加到引用属性的查询方法并提供排序方向(Asc或Desc)来应用静态排序。要创建支持动态排序的查询方法,请参阅“特殊参数处理”。
4.4.3.属性表达式
属性表达式只能引用托管实体的直接属性,如前面的示例所示。在创建查询时,您已经确保解析的属性是托管域类的属性。但是,您也可以通过遍历嵌套属性来定义约束。考虑以下方法签名:
List<Person> findByAddressZipCode(ZipCode zipCode);
假设 aPerson有Address一个ZipCode。在这种情况下,该方法会创建x.address.zipCode属性遍历。解析算法首先将整个部分 ( AddressZipCode)解释为属性,并检查域类中具有该名称(未大写)的属性。如果算法成功,它将使用该属性。如果不是,该算法将源在驼峰部分从右侧拆分为头部和尾部,并尝试找到相应的属性——在我们的示例中,AddressZip和Code。如果算法找到具有该头部的属性,它将获取尾部并继续从那里向下构建树,以刚才描述的方式将尾部拆分。如果第一个分割不匹配,算法将分割点向左移动 ( Address,ZipCode) 并继续。
虽然这应该适用于大多数情况,但算法可能会选择错误的属性。假设这个Person类也有一个addressZip属性。该算法已经在第一个拆分轮中匹配,选择了错误的属性,然后失败(因为 的类型addressZip可能没有code属性)。
要解决这种歧义,您可以_在方法名称中使用手动定义遍历点。所以我们的方法名称如下:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
因为我们将下划线字符视为保留字符,我们强烈建议遵循标准的 Java 命名约定(即,不在属性名称中使用下划线,而是使用驼峰式大小写)。
4.4.4.特殊参数处理
要处理查询中的参数,请定义前面示例中已经看到的方法参数。除此之外,该基础架构还可以识别某些特定类型,例如Pageableand Sort,以便动态地将分页和排序应用于您的查询。以下示例演示了这些功能:
示例 35.在查询方法中使用Pageable, Slice, 和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);
API 接受Sort并Pageable期望将非null值传递给方法。如果您不想应用任何排序或分页,请使用Sort.unsorted()and Pageable.unpaged()。
第一种方法允许您将
org.springframework.data.domain.Pageable实例传递给查询方法,以动态地将分页添加到静态定义的查询中。APage知道可用元素和页面的总数。它通过基础设施触发计数查询来计算总数来实现这一点。由于这可能很昂贵(取决于使用的商店),您可以改为返回Slice. ASlice只知道下一个Slice是否可用,这在遍历更大的结果集时可能就足够了。
排序选项也通过Pageable实例处理。如果您只需要排序,请
org.springframework.data.domain.Sort在您的方法中添加一个参数。如您所见,返回 aList也是可能的。在这种情况下,Page不会创建构建实际实例所需的额外元数据(这反过来意味着不会发出本来需要的额外计数查询)。相反,它将查询限制为仅查找给定范围的实体。
要了解整个查询获得了多少页,您必须触发额外的计数查询。默认情况下,此查询派生自您实际触发的查询。
分页和排序
您可以使用属性名称定义简单的排序表达式。您可以连接表达式以将多个条件收集到一个表达式中。
示例 36.定义排序表达式
Sort sort = Sort.by("firstname").ascending()
.and(Sort.by("lastname").descending());
要使用更安全的方式来定义排序表达式,请从要为其定义排序表达式的类型开始,并使用方法引用来定义要排序的属性。
示例 37. 使用类型安全 API 定义排序表达式
TypedSort<Person> person = Sort.sort(Person.class);
Sort sort = person.by(Person::getFirstname).ascending()
.and(person.by(Person::getLastname).descending());
TypedSort.by(…) 通过(通常)使用 CGlib 来使用运行时代理,这可能会在使用 Graal VM Native 等工具时干扰本机映像编译。
如果您的商店实现支持 Querydsl,您还可以使用生成的元模型类型来定义排序表达式:
示例 38. 使用 Querydsl API 定义排序表达式
QSort sort = QSort.by(QPerson.firstname.asc())
.and(QSort.by(QPerson.lastname.desc()));