
5.4.7. 仓库方法的空处理
从Spring Data 2.0开始,返回单个聚合实例的存储库CRUD方法使用Java 8来指示可能缺少值。 除此之外,Spring Data 还支持在查询方法上返回以下包装器类型:Optional
-
com.google.common.base.Optional -
scala.Option -
io.vavr.control.Option
或者,查询方法可以选择根本不使用包装器类型。 然后通过返回来指示缺少查询结果。 返回集合、集合替代项、包装器和流的存储库方法保证永远不会返回,而是返回相应的空表示形式。 有关详细信息,请参阅 “存储库查询返回类型”。nullnull
可为空性注释
您可以使用 Spring Framework 的可空性注释来表达存储库方法的可空性约束。 它们提供了一种工具友好的方法,并在运行时进行选择加入,如下所示:null
- @NonNullApi:在包级别用于声明参数和返回值的默认行为分别是既不接受也不生成值。null
- @NonNull:用于不得使用的参数或返回值(在适用的参数和返回值上不需要)。null@NonNullApi
- @Nullable:用于可以的参数或返回值。null
Spring 注释是用 JSR305注释(一种休眠但广泛使用的 JSR)进行元注释的。 JSR 305元注解允许工具供应商(如IDEA,Eclipse和Kotlin)以通用方式提供空安全支持,而不必对Spring注解进行硬编码支持。 要启用查询方法的可空性约束的运行时检查,您需要使用 Spring'sin在包级别激活非可空性,如以下示例所示:@NonNullApipackage-info.java
例 41。声明 中的非可空性package-info.java
@org.springframework.lang.NonNullApi
package com.acme;
一旦非 null 默认值到位,存储库查询方法调用将在运行时验证可空性约束。 如果查询结果违反定义的约束,则会引发异常。 当方法将返回但被声明为不可为空(默认值,在存储库所在的包上定义的注释)时,会发生这种情况。 如果要再次选择加入可为空的结果,请有选择地使用单个方法。 使用本节开头提到的结果包装器类型将继续按预期工作:空结果将转换为表示缺席的值。null@Nullable
以下示例显示了刚才描述的许多技术:
例 42。使用不同的可为空性约束
package com.acme;
interface UserRepository extends Repository<User, Long> {
User getByEmailAddress(EmailAddress emailAddress);
@Nullable
User findByEmailAddress(@Nullable EmailAddress emailAdress);
Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress);
}
存储库驻留在我们为其定义了非空行为的包(或子包)中。 |
当查询未生成结果时引发。 扔一个当手到方法就是。 |
当查询未生成结果时返回。 也接受作为值。 |
当查询未生成结果时返回。 扔一个当手到方法就是。 |
基于 Kotlin 的存储库中的可空性
Kotlin 将可空性约束的定义融入到语言中。 Kotlin 代码编译为字节码,字节码不通过方法签名表示可空性约束,而是通过编译的元数据来表达可空性约束。 确保在您的项目中包含 JAR,以便能够内省 Kotlin 的可空性约束。 Spring 数据存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:kotlin-reflect
例 43。在 Kotlin 存储库上使用可空性约束
interface UserRepository : Repository<User, String> {
fun findByUsername(username: String): User
fun findByFirstname(firstname: String?): User?
}该方法将参数和结果定义为不可为空(Kotlin 默认值)。 Kotlin 编译器拒绝传递给该方法的方法调用。 如果查询产生空结果,则引发 anis 。 |
此方法接受参数并返回查询未产生结果。 |
5.4.8. 流式查询结果
您可以使用 Java 8 作为返回类型以增量方式处理查询方法的结果。 不是将查询结果包装在 中,而是使用特定于数据存储的方法执行流式处理,如以下示例所示:Stream<T>Stream
例 44。使用 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);可能会包装特定于基础数据存储的资源,因此必须在使用后关闭。 您可以使用该方法或使用 Java 7block 手动关闭,如以下示例所示: |
例 45。在块中使用结果Stream<T>try-with-resources
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}并非所有 Spring 数据模块当前都支持返回类型。 |
5.4.9. 异步查询结果
您可以使用Spring 的异步方法运行功能异步运行存储库查询。 这意味着该方法在调用时立即返回,而实际查询发生在已提交给 Spring 的任务中。 异步查询不同于反应式查询,不应混合使用。 有关反应式支持的更多详细信息,请参阅特定于商店的文档。 以下示例显示了许多异步查询:TaskExecutor
@Async
Future<User> findByFirstname(String firstname);
@Async
CompletableFuture<User> findOneByFirstname(String firstname);
用作返回类型。 | |
使用 Java 8 作为返回类型。 |
5.5. 创建存储库实例
本节介绍如何为定义的存储库接口创建实例和 Bean 定义。
5.5.1. Java 配置
使用 Java 配置类上的特定于存储的注释来定义存储库激活的配置。 有关 Spring 容器的基于 Java 的配置的介绍,请参阅Spring 参考文档中的 JavaConfig。@EnableJpaRepositories
启用 Spring 数据存储库的示例配置类似于以下内容:
例 46。基于注释的存储库配置示例
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
前面的示例使用特定于 JPA 的注释,您将根据实际使用的存储模块对其进行更改。这同样适用于thebean的定义。请参阅涵盖特定于商店的配置的部分。 |
5.5.2.XML 配置
每个 Spring 数据模块都包含一个元素,该元素允许您定义 Spring 为您扫描的基本包,如以下示例所示:repositories
例 47。通过XML启用Spring 数据存储库
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jpa:repositories base-package="com.acme.repositories" />
</beans:beans>
在前面的示例中,指示 Spring 扫描其所有子包以查找接口扩展或其子接口之一。 对于找到的每个接口,基础结构都会注册特定于持久性技术,以创建处理查询方法调用的相应代理。 每个 Bean 都注册在从接口名称派生的 Bean 名称下,因此将注册一个接口。 嵌套存储库接口的 Bean 名称以其封闭类型名称为前缀。 基本包属性允许通配符,以便您可以定义扫描包的模式。com.acme.repositoriesRepositoryFactoryBeanUserRepositoryuserRepository
5.5.3. 使用过滤器
缺省情况下,基础结构选取扩展位于配置的基础包下的特定于持久性技术的子接口的每个接口,并为其创建一个 Bean 实例。 但是,您可能希望更精细地控制哪些接口为其创建了 Bean 实例。 为此,请在存储库声明中使用过滤器元素。 语义与 Spring 组件过滤器中的元素完全相同。 有关详细信息,请参阅这些元素的Spring 参考文档。Repository
例如,要从实例化中排除某些接口作为存储库 Bean,您可以使用以下配置:
例 48。使用过滤器
爪哇岛
.XML
@Configuration
@EnableJpaRepositories(basePackages = "com.acme.repositories",
includeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*SomeRepository") },
excludeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*SomeOtherRepository") })
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
前面的示例排除了以实例化结尾的所有接口,并包括以 结尾的接口。SomeRepositorySomeOtherRepository
5.5.4. 独立使用
您还可以在 Spring 容器之外使用存储库基础架构,例如,在 CDI 环境中。你的类路径中仍然需要一些 Spring 库,但通常,你也可以以编程方式设置存储库。提供存储库支持的 Spring 数据模块附带了您可以使用的特定于持久性技术的模块,如下所示:RepositoryFactory
例 49。存储库工厂的独立使用
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
5.6. Spring 数据存储库的自定义实现
Spring Data 提供了各种选项来创建查询方法,只需很少的编码。 但是,当这些选项不符合您的需求时,您还可以为存储库方法提供自己的自定义实现。 本节介绍如何执行此操作。
5.6.1. 自定义单个仓库
要使用自定义功能丰富存储库,您必须首先定义片段接口和自定义功能的实现,如下所示:
例 50。自定义存储库功能的界面
interface CustomizedUserRepository {
void someCustomMethod(User user);
}例 51。实现自定义存储库功能
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}与片段接口对应的类名中最重要的部分是后缀。 |
实现本身不依赖于 Spring 数据,可以是常规的 Spring bean。 因此,您可以使用标准的依赖注入行为来注入对其他 Bean 的引用(例如 a)、参与方面等。JdbcTemplate
然后,您可以让存储库接口扩展片段接口,如下所示:
例 52。对存储库界面的更改
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}使用存储库接口扩展片段接口结合了 CRUD 和自定义功能,并使其可供客户端使用。
Spring 数据存储库是通过使用形成存储库组合的片段来实现的。 片段是基本存储库、功能方面(如QueryDsl)和自定义接口及其实现。 每次向存储库界面添加接口时,都会通过添加片段来增强组合。 基本存储库和存储库方面实现由每个 Spring 数据模块提供。
以下示例显示了自定义接口及其实现:
例 53。片段及其实现
interface HumanRepository {
void someHumanMethod(User user);
}
class HumanRepositoryImpl implements HumanRepository {
public void someHumanMethod(User user) {
// Your custom implementation
}
}
interface ContactRepository {
void someContactMethod(User user);
User anotherContactMethod(User user);
}
class ContactRepositoryImpl implements ContactRepository {
public void someContactMethod(User user) {
// Your custom implementation
}
public User anotherContactMethod(User user) {
// Your custom implementation
}
}以下示例显示了扩展的自定义存储库的接口:CrudRepository
例 54。对存储库界面的更改
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// Declare query methods here
}存储库可以由多个自定义实现组成,这些实现按其声明顺序导入。 自定义实现的优先级高于基本实现和存储库方面。 此排序允许您覆盖基本存储库和方面方法,并在两个片段提供相同的方法签名时解决歧义。 存储库片段不限于在单个存储库界面中使用。 多个存储库可以使用片段界面,允许您跨不同存储库重用自定义项。
以下示例显示了存储库片段及其实现:
例 55。片段覆盖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
}
}以下示例显示了使用上述存储库片段的存储库:
例 56。自定义存储库接口
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}
interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}配置
存储库基础结构尝试通过扫描找到存储库的包下的类来自动检测自定义实现片段。 这些类需要遵循附加默认后缀的命名约定。Impl
以下示例显示了一个使用默认后缀的存储库和一个为后缀设置自定义值的存储库:
例 57。配置示例
爪哇岛
.XML
@EnableJpaRepositories(repositoryImplementationPostfix = "MyPostfix")
class Configuration { … }
前面示例中的第一个配置尝试查找调用充当自定义存储库实现的类。 第二个示例尝试查找。com.acme.repository.CustomizedUserRepositoryImplcom.acme.repository.CustomizedUserRepositoryMyPostfix
歧义的解决
如果在不同的包中找到具有匹配类名的多个实现,Spring Data 将使用 bean 名称来标识要使用的实现。
给定前面所示的以下两个自定义实现,将使用第一个实现。 它的 Bean 名称是,与片段接口 () 加上后缀的名称相匹配。CustomizedUserRepositorycustomizedUserRepositoryImplCustomizedUserRepositoryImpl
例 58。解决不明确的实现
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
}
如果您注释接口,则 bean 名称 plusthen 与为存储库实现定义的名称匹配,并且使用它代替第一个。UserRepository@Component("specialCustom")Implcom.acme.impl.two
手动接线
如果您的自定义实现仅使用基于注释的配置和自动连线,则上述方法效果很好,因为它被视为任何其他 Spring Bean。 如果您的实现片段 Bean 需要特殊连接,则可以声明 Bean 并根据上一节中描述的约定对其进行命名。 然后,基础结构按名称引用手动定义的 Bean 定义,而不是自己创建一个。 以下示例演示如何手动连接自定义实现:
例 59。自定义实现的手动接线
爪哇岛
.XML
class MyClass {
MyClass(@Qualifier("userRepositoryImpl") UserRepository userRepository) {
…
}
}5.6.2. 自定义基础仓库
当您想要自定义基本存储库行为以使所有存储库都受到影响时,上一节中描述的方法需要自定义每个存储库接口。 要改为更改所有存储库的行为,您可以创建一个实现来扩展特定于持久性技术的存储库基类。 然后,此类充当存储库代理的自定义基类,如以下示例所示:
例 60。自定义存储库基类
class MyRepositoryImpl<T, ID>
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 数据基础架构知道自定义的存储库基类。 在配置中,可以使用 来执行此操作,如以下示例所示:repositoryBaseClass
例 61。配置自定义存储库基类
爪哇岛
.XML
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
5.7. 从聚合根发布事件
存储库管理的实体是聚合根。 在域驱动设计应用程序中,这些聚合根通常发布域事件。 Spring Data 提供了一个注释,称为您可以在聚合根的方法上使用该注释,以使该发布尽可能简单,如以下示例所示:@DomainEvents
例 62。从聚合根公开域事件
class AnAggregateRoot {
@DomainEvents
Collection<Object> domainEvents() {
// … return events you want to get published here
}
@AfterDomainEventPublication
void callbackMethod() {
// … potentially clean up domain events list
}
}使用的方法可以返回单个事件实例或事件集合。 它绝不能接受任何论据。 |
发布所有事件后,我们有一个注释方法。 您可以使用它来潜在地清理要发布的事件列表(以及其他用途)。 |
每次调用 Spring 数据存储库或方法之一时都会调用这些方法。save(…)saveAll(…)delete(…)deleteAll(…)
5.8. 弹簧数据扩展
本节记录了一组 Spring 数据扩展,这些扩展允许在各种上下文中使用 Spring 数据。 目前,大多数集成都是针对Spring MVC的。
5.8.1. 查询扩展
Querydsl是一个框架,它支持通过其流畅的API构造静态类型的类似SQL的查询。
几个 Spring 数据模块提供与 Querydsl 的集成,如以下示例所示:QuerydslPredicateExecutor
例 63。QuerydslPredicateExecutor interface
public interface QuerydslPredicateExecutor<T> {
Optional<T> findById(Predicate predicate);
Iterable<T> findAll(Predicate predicate);
long count(Predicate predicate);
boolean exists(Predicate predicate);
// … more functionality omitted.
}查找并返回与 匹配的单个实体。 |
查找并返回与 匹配的所有实体。 |
返回与 匹配的实体数。 |
返回与实体匹配的实体是否存在。 |
要使用 Querydsl 支持,请扩展存储库接口,如以下示例所示:QuerydslPredicateExecutor
例 64。存储库上的 querydsl 集成
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}前面的示例允许您使用 Querydslinstances 编写类型安全的查询,如以下示例所示:Predicate
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);5.8.2. 网页支持
支持存储库编程模型的 Spring 数据模块附带了各种 Web 支持。 与Web相关的组件要求Spring MVC JAR位于类路径上。 其中一些甚至提供与Spring HATEOAS的集成。 通常,集成支持是通过在 JavaConfig 配置类中使用注释来启用的,如以下示例所示:@EnableSpringDataWebSupport
例 65。启用 Spring 数据网络支持
爪哇岛
.XML
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
注释注册了一些组件。 我们将在本节后面讨论这些内容。 它还检测类路径上的Spring HATEOAS,并为其注册集成组件(如果存在)。@EnableSpringDataWebSupport
基本网络支持
在XML中启用Spring Data Web支持
上一节中显示的配置注册了一些基本组件:
- A使用DomainClassConverter类让Spring MVC 从请求参数或路径变量解析存储库管理的域类的实例。
- HandlerMethodArgumentResolver实现,让 Spring MVC 从请求参数解析和实例。
PageableSort - 杰克逊模块,用于反序列化类型,或存储特定的类型,具体取决于所使用的 Spring 数据模块。
PointDistance
使用类DomainClassConverter
该类允许您直接在Spring MVC控制器方法签名中使用域类型,这样您就不需要通过存储库手动查找实例,如以下示例所示:DomainClassConverter
例 66。在方法签名中使用域类型的Spring MVC控制器
@Controller
@RequestMapping("/users")
class UserController {
@RequestMapping("/{id}")
String showUserForm(@PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "userForm";
}
}
该方法直接接收实例,无需进一步查找。 可以通过让Spring MVC首先将路径变量转换为域类的类型,并最终通过调用为域类型注册的存储库实例来访问实例来解决该实例。UseridfindById(…)
目前,存储库必须实现才有资格被发现进行转换。 |
用于可分页和排序的处理程序方法参数解析器
上一节中显示的配置片段还注册了 aas 以及 的实例。 注册启用 sandas 有效控制器方法参数,如以下示例所示:PageableHandlerMethodArgumentResolverSortHandlerMethodArgumentResolverPageableSort
例 67。使用可分页作为控制器方法参数
@Controller
@RequestMapping("/users")
class UserController {
private final UserRepository repository;
UserController(UserRepository repository) {
this.repository = repository;
}
@RequestMapping
String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", repository.findAll(pageable));
return "users";
}
}
前面的方法签名导致Spring MVC尝试使用以下默认配置从请求参数派生实例:Pageable
表 4.为实例评估的请求参数Pageable
| 要检索的页面。0 索引,默认为 0。 |
| 要检索的页面的大小。默认值为 20。 |
| 应按格式排序的属性。默认排序方向为区分大小写的升序。如果要切换方向或区分大小写,请使用多个参数,例如。 |
要定制此行为,请分别注册实现接口或接口的 Bean。 调用 Itsmethod,允许您更改设置,如以下示例所示:PageableHandlerMethodArgumentResolverCustomizerSortHandlerMethodArgumentResolverCustomizercustomize()
@Bean SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
return s -> s.setPropertyDelimiter("<-->");
}如果设置现有属性不足以满足您的目的,请扩展启用 HATEOAS 的等效项,覆盖理论方法,并导入自定义配置文件,而不是使用注释。MethodArgumentResolverSpringDataWebConfigurationpageableResolver()sortResolver()@Enable
如果您需要从请求中解析多个实例(例如,对于多个表),则可以使用 Spring'sannotation 来区分彼此。 然后,请求参数必须带有前缀。 下面的示例演示生成的方法签名:PageableSort@Qualifier${qualifier}_
String showUsers(Model model,
@Qualifier("thing1") Pageable first,
@Qualifier("thing2") Pageable second) { … }
您必须填充,等等。thing1_pagething2_page
默认传递给方法等效于 a,但您可以通过对参数使用注释来自定义它。PageablePageRequest.of(0, 20)@PageableDefaultPageable
对可分页的超媒体支持
Spring HATEOAS 附带了一个表示模型类 (),它允许使用必要的元数据和链接来丰富实例的内容,让客户端轻松浏览页面。 ato ais 的转换是通过 Spring HATEOAS接口的实现完成的,称为。 下面的示例演示如何使用 aas 控制器方法参数:PagedResourcesPagePagePagePagedResourcesResourceAssemblerPagedResourcesAssemblerPagedResourcesAssembler
例 68。使用 PagedResourcesAssembler 作为控制器方法参数
@Controller
class PersonController {
@Autowired PersonRepository repository;
@RequestMapping(value = "/persons", method = RequestMethod.GET)
HttpEntity<PagedResources<Person>> persons(Pageable pageable,
PagedResourcesAssembler assembler) {
Page<Person> persons = repository.findAll(pageable);
return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
}
}
启用配置(如前面的示例所示)允许将 thebe 用作控制器方法参数。 呼叫它具有以下效果:PagedResourcesAssemblertoResources(…)
- 的内容成为实例的内容。
PagePagedResources - 对象被附加一个实例,并填充来自底层的信息。
PagedResourcesPageMetadataPagePageRequest - 附上可能获取链接,具体取决于页面的状态。 链接指向方法映射到的 URI。 添加到方法的分页参数与设置匹配,以确保以后可以解析链接。
PagedResourcesprevnextPageableHandlerMethodArgumentResolver
假设数据库中有 30 个实例。 您现在可以触发请求 () 并查看类似于以下内容的输出:PersonGET http://localhost:8080/persons
{ "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
}
}汇编程序生成了正确的 URI,并且还选取了默认配置,以将参数解析为 afor 即将到来的请求。 这意味着,如果更改该配置,链接将自动遵循更改。 默认情况下,汇编程序指向在其中调用它的控制器方法,但您可以通过传递用作构建分页链接的基础的自定义来自定义该方法,这会重载该方法。PageableLinkPagedResourcesAssembler.toResource(…)
弹簧数据杰克逊模块
核心模块,以及一些特定于商店的模块,附带了一组杰克逊模块,用于Spring Data域使用的类似类型。
一旦启用了Web 支持并且可用,这些模块就会被导入。org.springframework.data.geo.Distanceorg.springframework.data.geo.Pointcom.fasterxml.jackson.databind.ObjectMapper
在初始化期间,就像 一样,由基础结构拾取,以便声明的 Jackson 可用。SpringDataJacksonModulesSpringDataJacksonConfigurationcom.fasterxml.jackson.databind.ModuleObjectMapper
以下域类型的数据绑定混合由通用基础结构注册。
org.springframework.data.geo.Distance
org.springframework.data.geo.Point
org.springframework.data.geo.Box
org.springframework.data.geo.Circle
org.springframework.data.geo.Polygon
单个模块可以提供额外的。 |
网页数据绑定支持
您可以使用 Spring Data 投影(在 [投影]中描述)通过使用 JSONPath 表达式(需要Jayway JsonPath)或XPath表达式(需要XmlBeam)来绑定传入的请求有效负载,如以下示例所示:
例 69。使用 JSONPath 或 XPath 表达式的 HTTP 有效负载绑定
@ProjectedPayload
public interface UserPayload {
@XBRead("//firstname")
@JsonPath("$..firstname")
String getFirstname();
@XBRead("/lastname")
@JsonPath({ "$.lastname", "$.user.lastname" })
String getLastname();
}
您可以使用前面示例中所示的类型作为 Spring MVC 处理程序方法参数,也可以使用 on 的方法之一。 前面的方法声明将尝试在给定文档中的任何地方查找。 XML 查找在传入文档的顶层执行。 它的 JSON 变体尝试顶级优先,但如果前者不返回值,也会尝试嵌套在子文档中。 这样,可以轻松缓解源文档结构中的更改,而无需客户端调用公开的方法(通常是基于类的有效负载绑定的缺点)。ParameterizedTypeReferenceRestTemplatefirstnamelastnamelastnamelastnameuser
支持嵌套投影,如 [投影] 中所述。 如果该方法返回复杂的非接口类型,则使用 Jacksonis 映射最终值。ObjectMapper
对于Spring MVC,一旦激活,就会自动注册必要的转换器,并且所需的依赖项在类路径上可用。 如需使用 ,请注册 (JSON) 或手动注册。@EnableSpringDataWebSupportRestTemplateProjectingJackson2HttpMessageConverterXmlBeamHttpMessageConverter
有关更多信息,请参阅规范的 Spring 数据示例存储库中的Web 投影示例。
查询网络支持
对于那些具有QueryDSL集成的存储,可以从查询字符串中包含的属性派生查询。Request
请考虑以下查询字符串:
?firstname=Dave&lastname=Matthews
给定前面示例中的对象,可以使用 将查询字符串解析为以下值,如下所示:UserQuerydslPredicateArgumentResolver
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))当在类路径上找到 Querydsl 时,将自动启用该功能。 |
将 ato 添加到方法签名提供了一个即用型,您可以使用 来运行。@QuerydslPredicatePredicateQuerydslPredicateExecutor
类型信息通常从方法的返回类型中解析。 由于该信息不一定与域类型匹配,因此最好使用 的属性。 |
下面的示例演示如何在方法中使用签名:@QuerydslPredicate
@Controller
class UserController {
@Autowired UserRepository repository;
@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate,
Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
将查询字符串参数解析为匹配对象。 |
默认绑定如下所示:
-
Object在简单属性上。eq -
Object在集合上像属性一样。contains -
Collection在简单属性上。in
您可以通过属性 ofor 自定义这些绑定,方法是使用 Java 8 并将方法添加到存储库接口,如下所示:bindings@QuerydslPredicatedefault methodsQuerydslBinderCustomizer
interface UserRepository extends CrudRepository<User, String>,
QuerydslPredicateExecutor<User>,
QuerydslBinderCustomizer<QUser> {
@Override
default void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(user.username).first((path, value) -> path.contains(value))
bindings.bind(String.class)
.first((StringPath path, String value) -> path.containsIgnoreCase(value));
bindings.excluding(user.password);
}
}
|
|
将属性的绑定定义为简单绑定。 |
将属性的默认绑定定义为不区分大小写的匹配项。 |
从解析中排除该属性。 |
您可以在从存储库或应用特定绑定之前注册持有默认 Querydsl 绑定的 abean。 |

5.8.3. 仓库填充器
如果您使用 Spring JDBC 模块,您可能熟悉对填充 SQL 脚本的支持。 类似的抽象在存储库级别可用,尽管它不使用 SQL 作为数据定义语言,因为它必须独立于存储。 因此,填充器支持XML(通过Spring的OXM抽象)和JSON(通过Jackson)来定义用于填充存储库的数据。DataSource
假设您有一个包含以下内容的文件:data.json
例 70。在 JSON 中定义的数据
[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]您可以使用 Spring 数据共享中提供的存储库命名空间的填充器元素来填充存储库。 若要将上述数据填充到 ur,请声明类似于以下内容的填充器:PersonRepository
例 71。声明杰克逊存储库填充器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
https://www.springframework.org/schema/data/repository/spring-repository.xsd">
<repository:jackson2-populator locations="classpath:data.json" />
</beans>
前面的声明会导致 Jackson 读取和反序列化该文件。data.jsonObjectMapper
JSON 对象解组的类型是通过检查 JSON 文档的属性来确定的。 基础结构最终会选择适当的存储库来处理反序列化的对象。_class
要改用 XML 来定义存储库应填充的数据,您可以使用 theelement。 您可以将其配置为使用 Spring OXM 中可用的 XML 编组选项之一。有关详细信息,请参阅Spring 参考文档。 以下示例显示如何使用 JAXB 取消编组存储库填充器:unmarshaller-populator
例 72。声明解组存储库填充器(使用 JAXB)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
https://www.springframework.org/schema/data/repository/spring-repository.xsd
http://www.springframework.org/schema/oxm
https://www.springframework.org/schema/oxm/spring-oxm.xsd">
<repository:unmarshaller-populator locations="classpath:data.json"
unmarshaller-ref="unmarshaller" />
<oxm:jaxb2-marshaller contextPath="com.acme" />
</beans>
6. 沙发库存储库
Spring 数据存储库抽象的目标是显著减少为各种持久性存储实现数据访问层所需的样板代码量。
默认情况下,如果操作是单文档操作并且 ID 已知,则由键/值支持。对于所有其他操作,默认情况下会生成 N1QL 查询,因此必须为高性能数据访问创建适当的索引。
请注意,您可以调整查询所需的一致性(请参阅使用一致性进行查询),并让不同的存储库由不同的存储桶提供支持(请参阅 [couchbase.repository.multibucket])
6.1. 配置
虽然始终存在对存储库的支持,但您需要为一般或特定命名空间启用它们。 如果你扩展,只需使用注释。 它提供了许多可能的选项来缩小或自定义搜索路径,最常见的选项之一是。AbstractCouchbaseConfiguration@EnableCouchbaseRepositoriesbasePackages
另请注意,如果您在 Spring 引导中运行,则自动配置支持已经为您设置了注释,因此只有在要覆盖默认值时才需要使用它。
例 73。基于注释的存储库设置
@Configuration
@EnableCouchbaseRepositories(basePackages = {"com.couchbase.example.repos"})
public class Config extends AbstractCouchbaseConfiguration {
//...
}
高级用法在[couchbase.repository.multibucket]中描述。
6.2. 用法
在最简单的情况下,您的存储库将扩展 其中 T 是您要公开的实体。 让我们看一下用户信息的存储库:CrudRepository<T, String>
例 74。用户信息存储库
public interface UserRepository extends CrudRepository<UserInfo, String> {
}请注意,这只是一个接口,而不是一个实际的类。 在后台,当您的上下文初始化时,将创建存储库描述的实际实现,并且您可以通过常规 bean 访问它们。 这意味着您将节省大量样板代码,同时仍向服务层和应用程序公开完整的 CRUD 语义。
现在,让我们想象一个使用它的类。 我们有哪些方法可用?@AutowireUserRepository
表 5.用户存储库上公开的方法
方法 | 描述 |
用户信息保存(用户信息实体) | 保存给定的实体。 |
可迭代<用户信息>保存(可迭代<用户信息>实体) | 保存实体列表。 |
用户信息查找一(字符串 ID) | 按实体的唯一 ID 查找实体。 |
布尔存在(字符串 ID) | 检查给定实体是否存在其唯一 ID。 |
Iterable<UserInfo> findAll() | 在存储桶中按此类型查找所有实体。 |
Iterable<UserInfo> findAll(Iterable<String> ids) | 按此类型和给定的 ID 列表查找所有实体。 |
长计数() | 计算存储桶中的实体数量。 |
无效删除(字符串 ID) | 按实体 ID 删除实体。 |
无效删除(用户信息实体) | 删除实体。 |
无效删除(可迭代<用户信息>实体) | 删除所有给定的实体。 |
无效删除全部() | 按类型删除存储桶中的所有实体。 |
现在这太棒了! 只需定义一个接口,我们就可以在托管实体之上获得完整的 CRUD 功能。
虽然公开的方法为您提供了各种各样的访问模式,但您通常需要定义自定义模式。 您可以通过向接口添加方法声明来执行此操作,这些声明将自动解析为后台的请求,我们将在下一节中看到。
6.3. 仓库和查询
6.3.1. 基于 N1QL 的查询
先决条件是在将存储实体的存储桶上创建一个主索引。
下面是一个示例:
例 75。具有 N1QL 查询的扩展用户信息存储库
public interface UserRepository extends CrudRepository<UserInfo, String> {
@Query("#{#n1ql.selectEntity} WHERE role = 'admin' AND #{#n1ql.filter}")
List<UserInfo> findAllAdmins();
List<UserInfo> findByFirstname(String fname);
}在这里,我们看到两种 N1QL 支持的查询方式。
第一种方法使用注释来提供内联的 N1QL 语句。 SpEL(Spring 表达式语言)由周围的 SpEL 表达式块支持。 一些特定于 N1QL 的值通过 SpEL 提供:Query#{}
-
#n1ql.selectEntity允许轻松确保语句将选择构建完整实体所需的所有字段(包括文档 ID 和 CAS 值)。 -
#n1ql.filter在 WHERE 子句中添加了一个与实体类型与 Spring Data 用于存储类型信息的字段相匹配的条件。 -
#n1ql.bucket将替换为存储实体的存储桶的名称,在反引号中转义。 -
#n1ql.scope将替换为实体所在的范围的名称,在反引号中转义。 -
#n1ql.collection将替换为存储实体的集合的名称,在反引号中转义。 -
#n1ql.fields将被重建实体所需的字段列表(例如,对于 SELECT 子句)替换。 -
#n1ql.delete将被语句替换。delete from -
#n1ql.returning将被重建实体所需的返回子句取代。
我们建议您始终将 SpEL 和 WHERE 子句与 aSpEL 一起使用(否则您的查询可能会受到其他存储库中的实体的影响)。 |
基于字符串的查询支持参数化查询。 您可以使用位置占位符(如“$1”),在这种情况下,每个方法参数将按顺序映射到,,...或者,可以使用“$someString”语法使用命名占位符。 方法参数将使用参数的名称与其相应的占位符匹配,可以通过注释每个参数(aor除外)来覆盖(例如)。 您不能在查询中混合使用这两种方法,并且会得到 anif 您这样做。$1$2$3PageableSort@Param@Param("someString")IllegalArgumentException
请注意,您可以混合使用 N1QL 占位符和 SpEL。N1QL 占位符仍将考虑所有方法参数,因此请务必使用正确的索引,如下例所示:
例 76。混合使用 SpEL 和 N1QL 占位符的内联查询
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND #{[0]} = $2")
public List<User> findUsersByDynamicCriteria(String criteriaField, Object criteriaValue)这允许您使用单个方法声明生成类似于 eg.or 的查询。AND name = "someName"AND age = 3
您还可以在 N1QL 查询中执行单个投影(前提是它只选择一个字段并仅返回一个结果,通常是像 ,,... 这样的聚合)。 这样的投影将具有一个简单的返回类型,例如,或。 这不适用于对 DTO 的投影。COUNTAVGMAXlongbooleanString
另一个例子:
相当于#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND test = $1SELECT #{#n1ql.fields} FROM #{#n1ql.collection} WHERE #{#n1ql.filter} AND test = $1
SpEL与弹簧安全性的实际应用
当您想要根据其他 Spring 组件(如 Spring 安全性)注入的数据进行查询时,SpEL 非常有用。 以下是扩展 SpEL 上下文以访问此类外部数据所需执行的操作。
首先,您需要实现一个(使用如下所示的支持类):EvaluationContextExtension
class SecurityEvaluationContextExtension extends EvaluationContextExtensionSupport {
@Override
public String getExtensionId() {
return "security";
}
@Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication) {};
}
}然后,要使 Spring Data Couchbase 能够访问关联的 SpEL 值,您需要做的就是在您的配置中声明一个相应的 bean:
@Bean
EvaluationContextExtension securityExtension() {
return new SecurityEvaluationContextExtension();
}
这对于根据连接用户的角色创建查询可能很有用,例如:
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND " +
"role = '?#{hasRole('ROLE_ADMIN') ? 'public_admin' : 'admin'}'")
List<UserInfo> findAllAdmins(); //only ROLE_ADMIN users will see hidden admins删除查询示例:
@Query("#{#n1ql.delete} WHERE #{#n1ql.filter} AND " +
"username = $1 #{#n1ql.returning}")
UserInfo removeUser(String username);第二种方法使用Spring-Data的查询派生机制,从方法名称和参数构建N1QL查询。 这将生成如下所示的查询: 您可以结合这些标准,甚至可以使用类似名称进行计数或使用名称类似进行限制......SELECT … FROM … WHERE firstName = "valueOfFnameAtRuntime"countByFirstnamefindFirst3ByLastname
实际上,生成的 N1QL 查询还将包含额外的 N1QL 条件,以便仅选择与存储库的实体类匹配的文档。 |
大多数 Spring-Data 关键字都受支持: .@Query (N1QL) 方法名称中支持的关键字
关键词 | 样本 | N1QL WHERE 子句片段 |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
您可以通过此方法同时使用计数查询和限制查询结果功能。
使用 N1QL,存储库的另一个可能的接口是 theone(可扩展)。 它添加了两种方法:PagingAndSortingRepositoryCrudRepository
表 6.分页和排序存储库中公开的方法
方法 | 描述 |
可迭代<T>查找全部(排序排序); | 允许在对其中一个属性进行排序时检索所有相关实体。 |
Page<T> findAll(可分页); | 允许在页面中检索您的实体。返回的允许轻松获取下一页以及项目列表。对于第一次调用,请使用为可分页。 |
您还可以将 andas 方法返回类型与 N1QL 支持的存储库一起使用。 |
如果可分页和排序参数与内联查询一起使用,则内联查询本身不应有任何排序依据、限制或偏移子句,否则服务器将因格式不正确而拒绝查询。 |
6.3.2. 自动索引管理
默认情况下,用户应为其查询创建和管理最佳索引。特别是在开发的早期阶段,自动创建索引以快速开始可以派上用场。
对于 N1QL,提供了以下注释,这些注释需要附加到实体(在类或字段上):
-
@QueryIndexed:放置在字段上,表示此字段应是索引的一部分 -
@CompositeQueryIndex:放置在类上,表示应创建多个字段(复合)上的索引。 -
@CompositeQueryIndexes:如果应创建多个注释,则此注释将获取它们的列表。CompositeQueryIndex
例如,这是在实体上定义复合索引的方式:
例 77。两个字段上的复合索引,具有排序
@Document
@CompositeQueryIndex(fields = {"id", "name desc"})
public class Airline {
@Id
String id;
@QueryIndexed
String name;
@PersistenceConstructor
public Airline(String id, String name) {
this.id = id;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
}
默认情况下,索引创建处于禁用状态。如果要启用它,则需要在配置上覆盖它:
例 78。启用自动索引创建
@Override
protected boolean autoIndexCreation() {
return true;
}
6.3.3. 一致性查询
默认情况下,使用 N1QL 的存储库查询使用扫描一致性。这意味着结果会快速返回,但索引中的数据可能尚未包含以前写入的操作中的数据(称为最终一致性)。如果您需要查询的“准备自己的写入”语义,则需要使用注释。下面是一个示例:NOT_BOUNDED@ScanConsistency
例 79。使用不同的扫描一致性
@Repository
public interface AirportRepository extends PagingAndSortingRepository<Airport, String> {
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Iterable<Airport> findAll();
}
6.3.4. DTO预测
Spring 数据存储库通常在使用查询方法时返回域模型。 但是,有时,出于各种原因,您可能需要更改该模型的视图。 在本节中,您将学习如何定义投影以提供简化和简化的资源视图。
查看以下域模型:
@Entity
public class Person {
@Id @GeneratedValue
private Long id;
private String firstName, lastName;
@OneToOne
private Address address;
…
}
@Entity
public class Address {
@Id @GeneratedValue
private Long id;
private String street, state, country;
…
}
这有几个属性:Person
-
id是主键 -
firstName安代尔数据属性lastName -
address是指向另一个域对象的链接
现在假设我们创建一个相应的存储库,如下所示:
interface PersonRepository extends CrudRepository<Person, Long> {
Person findPersonByFirstName(String firstName);
}Spring Data 将返回包含其所有属性的域对象。 有两个选项可以检索属性。 一种选择是为对象定义存储库,如下所示:addressAddress
interface AddressRepository extends CrudRepository<Address, Long> {}在这种情况下,using将仍然返回整个对象。 使用将返回只是。PersonRepositoryPersonAddressRepositoryAddress
但是,如果您根本不想暴露细节怎么办? 您可以通过定义一个或多个投影为存储库服务的使用者提供替代方案。address
例 80。简单投影
interface NoAddresses {
String getFirstName();
String getLastName();
}此投影具有以下详细信息:
一个普通的Java接口,使其具有声明性。 |
导出的。 |
导出的。 |
投影只有getters,这意味着它不会提供任何地址信息。 在这种情况下,查询方法定义返回而不是。NoAddressesfirstNamelastNameNoAdressesPerson
interface PersonRepository extends CrudRepository<Person, Long> {
NoAddresses findByFirstName(String firstName);
}投影声明基础类型与与公开属性相关的方法签名之间的协定。 因此,需要根据基础类型的属性名称命名 getter 方法。 如果基础属性被命名,则必须命名 getter 方法,否则 Spring Data 无法查找源属性。firstNamegetFirstName
7. 反应式沙发库存储库
7.1. 简介
本章介绍对 couchbase 的反应式存储库支持。 这建立在Couchbase 存储库中解释的核心存储库支持之上。 因此,请确保您对那里解释的基本概念有很好的理解。
7.2. 反应式合成库
Couchbase Java SDK 3.x从RxJava迁移到Reactor,因此它与反应式弹簧生态系统很好地融合在一起。
反应式 Couchbase 存储库提供项目 Reactor 包装器类型,只需从特定于库的存储库接口之一扩展即可使用:
- ReactiveCrudRepository
- ReactiveSortingRepository
7.3. 用法
让我们创建一个简单的实体来开始:
例 81。示例人员实体
public class Person {
@Id
private String id;
private String firstname;
private String lastname;
private Address address;
// … getters and setters omitted
}相应的存储库实现可能如下所示:
例 82。用于保留人员实体的基本存储库接口
public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Long> {
Flux<Person> findByFirstname(String firstname);
Flux<Person> findByFirstname(Publisher<String> firstname);
Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable);
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);
}对于JavaConfig,请使用theannotation。 注释带有与命名空间元素完全相同的属性。 如果未配置基本包,则基础结构将扫描带注释的配置类的包。@EnableReactiveCouchbaseRepositories
另请注意,如果您在 Spring 引导设置中使用它,您可能会省略注释,因为它是为您自动配置的。
例 83。JavaConfig for repository
@Configuration
@EnableReactiveCouchbaseRepositories
class ApplicationConfig extends AbstractCouchbaseConfiguration {
// ... (see configuration for details)
}
随着我们的域存储库的扩展,它为您提供了 CRUD 操作以及对实体进行排序访问的方法。 使用存储库实例只是将其注入客户端的依赖项问题。ReactiveSortingRepository
例 84。对个人实体的排序访问
public class PersonRepositoryTests {
@Autowired
ReactivePersonRepository repository;
@Test
public void sortsElementsCorrectly() {
Flux<Person> persons = repository.findAll(Sort.by(new Order(ASC, "lastname")));
assertNotNull(perons);
}
}7.4. 仓库和查询
Spring Data的Reactive Couchbase带有完全的查询支持,已经由阻塞存储库和查询提供
8. 模板和直接操作
该模板提供对基础数据库的较低级别访问权限,并用作存储库的基础。 任何时候,当存储库对于您的需求来说太高级别时,模板很有可能会很好地为您服务。请注意, 你总是可以通过 bean 上直接插入 SDK 的。AbstractCouchbaseConfiguration
8.1. 支持的操作
该模板可以通过上下文之外的 theandbeans 访问。 获得对它的引用后,您可以对它运行各种操作。 除了通过存储库之外,在模板中,您需要始终指定要转换的目标实体类型。couchbaseTemplatereactiveCouchbaseTemplate
这些模板使用流畅样式的 API,允许您根据需要链接可选运算符。举个例子,这里是 如何存储用户,然后通过其 ID 再次查找它:
例 85。流畅的模板访问
// Create an Entity
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
// Upsert it
couchbaseTemplate.upsertById(User.class).one(user);
// Retrieve it again
User found = couchbaseTemplate.findById(User.class).one(user.getId());
如果要对操作使用自定义持久性要求,可以将其链接在:upsert
例 86。具有耐用性的更新插入
User modified = couchbaseTemplate
.upsertById(User.class)
.withDurability(DurabilityLevel.MAJORITY)
.one(user);
以类似的方式,您可以执行 N1QL 操作:
例 87。模板上的 N1QL 查询
final List<User> foundUsers = couchbaseTemplate
.findByQuery(User.class)
.consistentWith(QueryScanConsistency.REQUEST_PLUS)
.all();
9. 沙发库交易
Couchbase 支持分布式事务。本节记录了如何将其与Spring Data Couchbase一起使用。
9.1. 要求
- 沙发基础服务器 6.6.1 或更高版本。
- 弹簧数据沙发库 5.0.0-M5 或更高版本。
- 应配置 NTP,以便 Couchbase 群集的节点与时间同步。不同步的时间不会导致不正确的行为,但可能会影响元数据清理。
- 在application.properties中设置spring.main.allow-bean-definition-overriding=true,或者作为SpringApplicationBuilder属性。
9.2. 概述
Spring Data Couchbase 模板操作插入、查找、替换和删除,使用这些调用的存储库方法可以参与 Couchbase 事务。它们可以通过使用@Transactional注释、CouchbaseTransactionalOperator 或在 Couchbase 事务的 lambda 中在事务中执行。
9.3. 入门和配置
Couchbase 交易通常使用带有 @Transactional 注释的方法进行杠杆处理。 @Transactional运算符是使用 CouchbaseTransactionManager 实现的,CouchbaseTransactionManager 在 AbstractCouchbaseConfiguration 中作为 bean 提供。 Couchbase Transactions 无需定义服务类即可使用 CouchbaseTransactionOperator,CouchbaseTransactionOperator 在 AbtractCouchbaseConfiguration 中也作为 bean 提供。 Couchbase Transactions 也可以直接使用 Spring Data Couchbase 操作 在 lambda中使用事务
9.4. 与@Transactional的交易
@Transactional将类上的方法或所有方法定义为事务性方法。
在类级别声明此注释时,它作为默认值应用。 声明类及其子类的所有方法。
9.4.1. 属性语义
在此版本中,Couchbase 事务忽略回滚属性。 事务隔离级别是读取提交的;
例 88。按@Transactional配置和使用事务
配置的
@Configuration
@EnableCouchbaseRepositories("<parent-dir-of-repository-interfaces>")
@EnableReactiveCouchbaseRepositories("<parent-dir-of-repository-interfaces>")
@EnableTransactionManagement
static class Config extends AbstractCouchbaseConfiguration {
// Usual Setup
@Override public String getConnectionString() { /* ... */ }
@Override public String getUserName() { /* ... */ }
@Override public String getPassword() { /* ... */ }
@Override public String getBucketName() { /* ... */ }
// Customization of transaction behavior is via the configureEnvironment() method
@Override protected void configureEnvironment(final Builder builder) {
builder.transactionsConfig(
TransactionsConfig.builder().timeout(Duration.ofSeconds(30)));
}
}
事务性服务类
请注意,如果事务失败,可以重新执行@Transactional方法的主体。 方法主体中的所有内容都必须是幂等的。
final CouchbaseOperations personOperations;
final ReactiveCouchbaseOperations reactivePersonOperations;
@Service
public class PersonService {
final CouchbaseOperations operations;
final ReactiveCouchbaseOperations reactiveOperations;
public PersonService(CouchbaseOperations ops, ReactiveCouchbaseOperations reactiveOps) {
operations = ops;
reactiveOperations = reactiveOps;
}
// no annotation results in this method being executed not in a transaction
public Person save(Person p) {
return operations.save(p);
}
@Transactional
public Person changeFirstName(String id, String newFirstName) {
Person p = operations.findById(Person.class).one(id);
return operations.replaceById(Person.class).one(p.withFirstName(newFirstName);
}
@Transactional
public Mono<Person> reactiveChangeFirstName(String id, String newFirstName) {
return personOperationsRx.findById(Person.class).one(person.id())
.flatMap(p -> personOperationsRx.replaceById(Person.class).one(p.withFirstName(newFirstName)));
}
}
使用@Transactional服务。
@Autowired PersonService personService;
Person walterWhite = new Person( "Walter", "White");
Person p = personService.save(walterWhite); // this is not a transactional method
...
Person renamedPerson = personService.changeFirstName(walterWhite.getId(), "Ricky");
@Transactional方法注释的功能需要
- 要用@EnableTransactionManagement注释的配置类;
- 具有带注释方法的服务对象必须使用 @Service 进行注释;
- 方法的主体在事务中执行。
- 必须通过@Autowired获取具有注释方法的服务对象。
- 对该方法的调用必须从与服务不同的类进行,因为调用 来自同一类的方法不会调用执行事务处理的方法拦截器。
9.5. 与 CouchbaseTransactionalOperator 的交易
CouchbaseTransactionalOperator 可用于内联构造事务,而无需创建使用 @Transactional 的服务类。 CouchbaseTransactionalOperator 可用作 bean,可以使用 @Autowired 实例化。 如果显式创建一个,则必须使用 CouchbaseTransactionalOperator.create(manager) (NOT TransactionalOperator.create(manager)) 创建它。
例 89。使用 TransactionalOperator.execute() 进行事务访问
@Autowired TransactionalOperator txOperator;
@Autowired ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
Flux<Person> result = txOperator.execute((ctx) ->
reactiveCouchbaseTemplate.findById(Person.class).one(person.id())
.flatMap(p -> reactiveCouchbaseTemplate.replaceById(Person.class).one(p.withFirstName("Walt")))
);
9.6. 直接使用 SDK 进行交易
Spring Data Couchbase 与 Couchbase Java SDK 无缝协作,用于事务处理。弹簧数据沙发库操作 可以在事务中执行,将直接在 Output().run() 的 lambda 中工作,而不涉及任何 Spring 交易机制。这是在Spring Data Couchbase中利用Couchbase Transactions的最直接方法。
请参阅参考文档
例 90。事务访问 - 阻止
@Autowired CouchbaseTemplate couchbaseTemplate;
TransactionResult result = couchbaseTemplate.getCouchbaseClientFactory().getCluster().transactions().run(ctx -> {
Person p = couchbaseTemplate.findById(Person.class).one(personId);
couchbaseTemplate.replaceById(Person.class).one(p.withFirstName("Walt"));
});
例 91。事务访问 - 反应式
@Autowired ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
Mono<TransactionResult> result = reactiveCouchbaseTemplate.getCouchbaseClientFactory().getCluster().reactive().transactions()
.run(ctx ->
reactiveCouchbaseTemplate.findById(Person.class).one(personId)
.flatMap(p -> reactiveCouchbaseTemplate.replaceById(Person.class).one(p.withFirstName("Walt")))
);
10. 催收支持
Couchbase 支持作用域和集合。本节介绍如何将其与Spring Data Couchbase一起使用。
try-cb-spring示例应用程序是在Spring Data Couchbase中使用Scopes and Collections的工作示例。
2021 Couchbase Connect 关于春季数据集合的演示文稿可以在仅演示文稿 和带幻灯片的演示文稿
10.1. 要求
- 沙发基础服务器 7.0 或更高版本。
- Spring Data Couchbase 4.3.1 或更高版本。
10.2. 入门和配置
10.2.1. 范围和集合规范
有几种机制可以指定作用域和集合,这些机制可以组合在一起,或者一种机制可以覆盖另一种机制。 首先是范围和集合的一些定义。未指定的作用域表示将使用默认作用域,同样,一个 未指定的集合指示将使用默认集合。 只有三种有效的范围和集合组合。(1)默认范围和默认集合;(2) 默认 范围和非默认集合;以及 (3) 非默认范围和非默认集合。不可能有非默认值 作用域和默认集合作为非默认作用域不包含默认集合,也不能创建默认集合。
可以在配置中指定范围:
@Configuration
static class Config extends AbstractCouchbaseConfiguration {
// Usual Setup
@Override public String getConnectionString() { /* ... */ }
// optionally specify the scope in the Configuration
@Override
protected String getScopeName() {
return "myScope"; // or a variable etc.;
}
}
范围和集合可以指定为实体类和存储库的注释:
@Document
@Scope("travel")
@Collection("airport")
public class Airport {...
@Scope("travel")
@Collection("airport")
public interface AirportRepository extends CouchbaseRepository<Airport, String> ...可以使用 inScope(scopeName) 和 inCollection(collectionName) fluent API 在模板上指定范围和集合:
List<Airport> airports = template.findByQuery(Airport.class).inScope("archived").all()可以在使用 withScope(scopeName) 和 withCollection(collectionName) API 扩展 DynamicProxyable 的存储库上指定范围和集合:
public interface AirportRepository extends CouchbaseRepository<Airport, String>, DynamicProxyable<AirportRepository>{...}
...
List<Airport> airports = airportRepository.withScope("archived").findByName(iata);优先级顺序为:
- inScope()/inCollection() 的模板 fluent api
- 模板/存储库对象的 withScope()/withCollection()
- 存储库方法的注释
- 存储库界面的注释
- 实体对象的批注
- 配置的 getScope()
11. 沙发基字段级加密
Couchbase 支持字段级加密。本节记录了如何将其与Spring Data Couchbase一起使用。
11.1. 要求
- Spring Data Couchbase 5.0.0-RC1 或更高版本。
11.2. 概述
使用 com.couchbase.client.java.encryption.annotation.Encrypted (@Encrypted) 注释的字段将在写入时自动加密,在读取时自动解密。未加密的字段可以通过指定 @Encrypted(迁移 = Encrypted.Migration.FROM_UNENCRYPTED) 迁移到加密字段。
11.3. 入门和配置
11.3.1. 依赖关系
字段级加密可用于依赖项(请参阅字段级加密 )
<groupId>com.couchbase.client</groupId>
<artifactId>couchbase-encryption</artifactId>
HashiCorp Vault Transit集成需要Spring Vault
<groupId>org.springframework.vault</groupId>
<artifactId>spring-vault-core</artifactId>
11.3.2. 提供加密管理器
CryptoManager 需要通过覆盖 AbstractCouchbaseConfiguration 中的 cryptoManager() 方法来提供。Spring Data Couchbase 以及从 CouchbaseClientFactory 进行的 Couchbase Java SDK 直接调用将使用此 CryptoManager。
@Override
protected CryptoManager cryptoManager() {
KeyStore javaKeyStore = KeyStore.getInstance("MyKeyStoreType");
FileInputStream fis = new java.io.FileInputStream("keyStoreName");
char[] password = { 'a', 'b', 'c' };
javaKeyStore.load(fis, password);
Keyring keyring = new KeyStoreKeyring(javaKeyStore, keyName -> "swordfish");
// AES-256 authenticated with HMAC SHA-512. Requires a 64-byte key.
AeadAes256CbcHmacSha512Provider provider = AeadAes256CbcHmacSha512Provider.builder().keyring(keyring).build();
CryptoManager cryptoManager = DefaultCryptoManager.builder().decrypter(provider.decrypter())
.defaultEncrypter(provider.encrypterForKey("myKey")).build();
}
11.3.3. 将字段定义为加密字段。
- @Encrypted将字段定义为已加密。
- @Encrypted(迁移 = Encrypted.Migration.FROM_UNENCRYPTED) 定义一个字段,读取时可能会也可能不会加密。写入时将被加密。
- @Encrypted(encrypter = “<encrypterAlias>”) 指定用于加密的加密器的别名。请注意,这不是算法,而是将加密器添加到加密管理器时指定的名称。
11.3.4. 例子
例 92。抽象沙发基地配置
@Configuration
@EnableCouchbaseRepositories("<parent-dir-of-repository-interfaces>")
@EnableReactiveCouchbaseRepositories("<parent-dir-of-repository-interfaces>")
static class Config extends AbstractCouchbaseConfiguration {
// Usual Setup
@Override public String getConnectionString() { /* ... */ }
@Override public String getUserName() { /* ... */ }
@Override public String getPassword() { /* ... */ }
@Override public String getBucketName() { /* ... */ }
/* provide a cryptoManager */
@Override
protected CryptoManager cryptoManager() {
KeyStore javaKeyStore = KeyStore.getInstance("MyKeyStoreType");
FileInputStream fis = new java.io.FileInputStream("keyStoreName");
char[] password = { 'a', 'b', 'c' };
javaKeyStore.load(fis, password);
Keyring keyring = new KeyStoreKeyring(javaKeyStore, keyName -> "swordfish");
// AES-256 authenticated with HMAC SHA-512. Requires a 64-byte key.
AeadAes256CbcHmacSha512Provider provider = AeadAes256CbcHmacSha512Provider.builder().keyring(keyring).build();
CryptoManager cryptoManager = DefaultCryptoManager.builder().decrypter(provider.decrypter())
.defaultEncrypter(provider.encrypterForKey("myKey")).build();
}
}
例 93。文档中的批注
@Document
public class AddressWithEncStreet extends Address {
private @Encrypted String encStreet;
.
.
例 94。代码中的用法
AddressWithEncStreet address = new AddressWithEncStreet(); // plaintext address with encrypted street
address.setCity("Santa Clara");
address.setEncStreet("Olcott Street");
addressEncryptedRepository.save(address);
例 95。生成的文档
{
"_class": "AddressWithEncStreet",
"city": "Santa Clara",
"encrypted$encStreet": {
"alg": "AEAD_AES_256_CBC_HMAC_SHA512",
"ciphertext": "A/tJALmtixTxqj77ZUcUgMklIt3372DKD7l5FvbCzHNJMplbgQEv0RgSbxIfiRNr+uW2H7cokkcCW/F5YnQoXA==",
"kid": "myKey"
}
}12. ANSI 加入
本章介绍如何跨实体使用 ANSI 联接。 从 5.5 版本开始,Couchbase 服务器支持 ANSI 联接,以使用字段联接文档。 以前的版本允许索引和查找联接,SDC 仅通过直接通过 SDK 查询来支持索引和查找联接。
跨存储库的实体之间的关系可以是一对一的,也可以是一对多的关系。 通过定义此类关系,可以获取关联实体的同步视图。
12.1. 配置
可以通过注释实体的属性引用来获取关联的实体。 前缀引用左侧键空间(当前实体),并引用右侧键空间(关联实体)。 必需的元素 forannotation 是 theclause,一个布尔表达式,表示左侧 () 和右侧 () 之间的连接条件,可以是字段、常量表达式或任何复杂的 N1QL 表达式。 还可以在连接的注释上指定一个可选子句,类似地用于引用当前实体和引用关联实体。@N1qlJoinlksrks@N1qlJoinonlksrkswherelksrks
例 96。ANSI 联接的注释
@Document
public class Author {
@Id
String id;
String name;
@N1qlJoin(on = "lks.name=rks.authorName")
List<Book> books;
@N1qlJoin(on = "lks.name=rks.name")
Address address;
...
}
12.2. 延迟获取
可以在首次访问属性时延迟提取关联的实体,这可以节省提取比加载实体时所需的更多数据。 要延迟加载关联的实体,必须将注释的元素设置为 。 默认值为。@N1qlJoinfetchTypeFetchType.LAZYFetchType.IMMEDIATE
例 97。延迟获取的配置
@N1qlJoin(on = "lks.name=rks.authorName", fetchType = FetchType.LAZY)
List<Book> books;
12.3. ANSI 连接提示
12.3.1. 使用索引提示
index元素可用于为(当前实体)索引提供提示,元素可用于提供(关联实体)索引。@N1qlJoinlksrightIndexrks
12.3.2. 哈希连接提示
如果连接类型将是哈希连接,则可以为 (关联实体) 指定哈希端。 如果关联的实体位于生成端,则可以将其指定为其他实体。rksHashSide.BUILDHashSide.PROBE
12.3.3. 使用按键提示
keys元素可用于指定唯一的文档键以限制连接键空间。@N1qlJoin
13. 缓存
本章介绍对缓存和的其他支持。@Cacheable
13.1. 配置和使用
从技术上讲,缓存不是 spring-data 的一部分,而是直接在 spring 核心中实现的。spring-data 包中的大多数数据库实现都不支持,因为不可能存储任意数据。@Cacheable
Couchbase 同时支持二进制数据和 JSON 数据,因此您可以从同一个数据库中获取两者。
要使其工作,您需要添加注释并配置 bean:@EnableCachingcacheManager
例 98.用于缓存AbstractCouchbaseConfiguration
@Configuration
@EnableCaching
public class Config extends AbstractCouchbaseConfiguration {
// general methods
@Bean
public CouchbaseCacheManager cacheManager(CouchbaseTemplate couchbaseTemplate) throws Exception {
CouchbaseCacheManager.CouchbaseCacheManagerBuilder builder = CouchbaseCacheManager.CouchbaseCacheManagerBuilder
.fromConnectionFactory(couchbaseTemplate.getCouchbaseClientFactory());
builder.withCacheConfiguration("mySpringCache", CouchbaseCacheConfiguration.defaultCacheConfig());
return builder.build();
}
然后,可以在注释上使用标识符来标识要使用的缓存管理器(您可以配置多个)。persistent@Cacheable
设置完成后,您可以使用注释注释每个方法,以透明地将其缓存在 couchbase 存储桶中。您还可以自定义密钥的生成方式。@Cacheable
例 99。缓存示例
@Cacheable(value="persistent", key="'longrunsim-'+#time")
public String simulateLongRun(long time) {
try {
Thread.sleep(time);
} catch(Exception ex) {
System.out.println("This shouldnt happen...");
}
return "I've slept " + time + " miliseconds.;
}
如果多次运行该方法,您将看到首先发生一个集合操作,然后是多个 get 操作,并且没有休眠时间(这伪造了昂贵的执行)。你可以存储任何你想要的东西,如果是JSON,当然你可以通过视图访问它,并在Web UI中查看它。
请注意,要使用 cache.clear() 或 catch.invalidate(),存储桶必须具有主键。 :水平偏移量:-1
14. 附录
附录 A:命名空间引用
元素<repositories />
该元素触发了 Spring 数据存储库基础结构的设置。最重要的属性是,它定义了要扫描 Spring 数据存储库接口的包。请参阅“XML 配置”。下表描述了元素的属性:<repositories />base-package<repositories />
表 7.属性
名字 | 描述 |
| 定义要扫描的包,以查找在自动检测模式下扩展的存储库接口(实际接口由特定的 Spring 数据模块确定)。还会扫描已配置软件包下的所有软件包。允许使用通配符。 |
| 定义用于自动检测自定义存储库实现的后缀。名称以配置的后缀结尾的类被视为候选类。默认为。 |
| 确定用于创建查找器查询的策略。有关详细信息,请参阅“查询查找策略”。默认为。 |
| 定义搜索包含外部定义查询的属性文件的位置。 |
| 是否应考虑嵌套存储库接口定义。默认为。 |
附录 B:填充器命名空间参考
<填充器 /> 元素
该元素允许通过 Spring 数据存储库基础架构填充数据存储。[1]<populator />
表 8.属性
名字 | 描述 |
| 应填充从存储库中查找要读取对象的文件的位置。 |
附录 C:存储库查询关键字
支持的查询方法主题关键字
下表列出了 Spring 数据存储库查询派生机制通常支持的主题关键字,以表达谓词。 有关支持的关键字的确切列表,请参阅特定于商店的文档,因为此处列出的某些关键字可能在特定商店中不受支持。
表 9.查询主题关键字
关键词 | 描述 |
| 常规查询方法通常返回存储库类型、主子类型或结果包装器(如),或任何其他特定于存储的结果包装器。可以用作,或与其他关键字结合使用。 |
| 存在投影,返回通常为结果。 |
| 计数投影返回数值结果。 |
| 删除查询方法不返回任何结果 () 或删除计数。 |
| 将查询结果限制为第一个结果。此关键字可以出现在主题(和其他关键字)之间的任何位置。 |
| 使用非重复查询仅返回唯一结果。请参阅特定于商店的文档是否支持该功能。此关键字可以出现在主题(和其他关键字)之间的任何位置。 |
支持的查询方法谓词关键字和修饰符
下表列出了 Spring 数据存储库查询派生机制通常支持的谓词关键字。 但是,请参阅特定于商店的文档,了解支持的关键字的确切列表,因为此处列出的某些关键字可能在特定商店中不受支持。
表 10.查询谓词关键字
逻辑关键字 | 关键字表达式 |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
除了筛选器谓词之外,还支持以下修饰符列表:
表 11.查询谓词修饰符关键字
关键词 | 描述 |
| 与谓词关键字一起使用,用于不区分大小写的比较。 |
| 忽略所有合适属性的大小写。在查询方法谓词中的某处使用。 |
| 指定静态排序顺序,后跟属性路径和方向(例如)。 |
附录 D:存储库查询返回类型
支持的查询返回类型
下表列出了 Spring 数据存储库通常支持的返回类型。 但是,请参阅特定于商店的文档以获取支持的返回类型的确切列表,因为此处列出的某些类型可能在特定商店中不受支持。
地理空间类型(如、和)仅适用于支持地理空间查询的数据存储。 某些存储模块可能会定义自己的结果包装器类型。 |
表 12.查询返回类型
返回类型 | 描述 |
| 表示无返回值。 |
原 | Java 原语。 |
包装器类型 | Java 包装器类型。 |
| 一个独特的实体。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an. |
| 一。 |
| 一个。 |
| 一个。 |
| 爪哇8或番石榴。期望查询方法最多返回一个结果。如果未找到结果,则返回 oris 。多个结果触发 an. |
| 要么是斯卡拉,要么是vavrtype。在语义上与前面描述的Java 8的行为相同。 |
| A Java 8 . |
| 该直接的便利扩展公开了流式传输,映射和过滤结果,连接它们等的方法。 |
实现和采用构造函数或工厂方法参数的类型 | 公开采用 aas 参数的构造函数或/工厂方法的类型。有关详细信息,请参阅返回自定义可流式传输包装器类型。 |
瓦夫尔,,, | Vavr 集合类型。有关详细信息,请参阅对 Vavr 集合的支持。 |
| A. 期望对方法进行注释,并且需要启用 Spring 的异步方法执行功能。 |
| A Java 8.期望对方法进行注释,并且需要启用 Spring 的异步方法执行功能。 |
| 一个大小的数据块,指示是否有更多可用数据。需要方法参数。 |
| A 包含其他信息,例如结果总数。需要方法参数。 |
| 包含附加信息(如到参考位置的距离)的结果条目。 |
| 包含附加信息的列表,例如到参考位置的平均距离。 |
| Awith,例如到参考位置的平均距离。 |
| 使用反应式存储库发射零个或一个元素的项目反应器。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an. |
| 使用反应式存储库发射零个、一个或多个元素的项目反应器。返回的查询还可以发出无限数量的元素。 |
| 一个 RxJava使用反应式存储库发出单个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an. |
| 使用反应式存储库的 RxJavaemitting 零个或一个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an. |
| 使用反应式存储库的 RxJavaemitting 零个、一个或多个元素。返回的查询还可以发出无限数量的元素。 |










