Spring Data 2020.0 - 新增和值得关注的功能 - 概览

工程 | Christoph Strobl | 2020 年 11 月 6 日 | ...

基于 Spring Framework 5.3 的 Spring Data 2020.0 现已发布,并为各种存储带来了大量新功能,这些功能已在各个模块中介绍。在发布里程碑公告的同时,我们想通过一系列博客文章更详细地介绍这些新功能。这些文章将涵盖,除其他外:

  • Data Commons 中的响应式上下文访问功能。
  • Spring R2DBC 中的生命周期回调和基线 R2DBC
  • 通用存储库和特定存储的 Redis 缓存指标。
  • Neo4J 响应式存储库.
  • Spring Data for Apache Geode 的分页存储库 API。
  • Spring Data Elasticsearch 的地理形状、索引和响应式搜索改进。
  • MongoDB 中的部分过滤器和聚合提示。
  • JDBC 模块的 @Bean 行映射器。
  • Apache Cassandra 抽象的每个语句的键空间定义。
  • 声明式 Couchbase N1QL 查询。

在本第一部分中,我们将花时间介绍一些通用主题以及 commons 模块中对存储特定实现有影响的某些方面。

我们从最明显的改变开始:版本方案。在遵循(纪念著名计算机科学家)的字母顺序超过 7 年后,我们切换到了基于 CalVer 的版本方案。这主要会影响那些通过 spring-data-releasetrain 伪项目管理 Spring Data 构件版本的人,该项目已更改为 spring-data-bom 和新的方案。

org.springframework.data
spring-data-bom
2020.0.0

各个模块继续使用数字版本方案。

尽管有这些基础设施方面的变化,但本次发布仍明确侧重于扩展响应式编程、增加应用洞察力、指标以及在使用 GraalVM 原始镜像时改善开发者体验。尽管如此,我们还是花时间对一些细节进行了调整,希望使您的日常工作更加便捷。

其中一项增强功能是为投影添加对 java.lang.Optional 的支持。这虽然不是什么颠覆性的改变,但它确实允许您在需要时避免 null 值。

interface User {

	String getNickname();
	Optional<byte[]> getPicture();
}

在进行性能洞察(稍后会详细介绍)的工作时,我们现在将存储库初始化推迟到首次使用时,这加快了应用程序的启动速度。

我们还花时间对整个生产代码库进行了 Delombok 处理,以提高可读性和调试性。

响应式上下文访问

本次发布为我们完成响应式故事贡献了又一个重要部分。响应式上下文的添加使我们能够处理响应式流程中的响应式审计和 SpEL 上下文扩展评估等功能。到目前为止,审计和 SpEL 在一定程度上是可行的。缺失的部分是访问上下文详细信息的能力,例如通常由 Spring Security 在 WebFlux 配置中提供的身份验证主体。

在处理上下文时,您有两种选择:

  • 随调用传递上下文
  • 带外存储上下文详细信息(例如,使用 ThreadLocal

审计和 SpEL 上下文扩展的命令式变体依赖于 ThreadLocal 存储。然而,订阅响应式管道会消除关于管道将在哪些线程上具体化的任何假设。因此,这种方法变得不可用。这时第二种选择就派上用场了,即随调用传递上下文详细信息。Project Reactor 的 Context 功能允许将上下文数据附加到订阅。要访问上下文,需要一个 API 来使用 Publisher 类型。因此,本次发布我们引入了用于审计的响应式 API 变体,即 ReactiveAuditorAwareReactiveEvaluationContextExtension

interface ReactiveAuditorAware<T> {  
	Mono<T> getCurrentAuditor();  
}

interface ReactiveEvaluationContextExtension extends ExtensionIdAware {  
	Mono<? extends EvaluationContextExtension> getExtension();  
}

您可以将这两种接口的实现公开为 Spring Bean。Spring Data 会拾取它们并分别用于响应式审计和上下文扩展。

Reactive Auditing(响应式审计)

响应式审计将可审计实体与时间戳和当前用户(“审计员”)关联起来。在插入和更新时,Spring Data 会填充用 @CreatedBy@LastModifiedDate 标注的属性。在以前的版本中,用 @CreatedDate@LastModifiedDate 标注的属性是通过实体回调更新的。从本次发布开始,审计员将通过专用的实体回调从 ReactiveAuditorAware 传播到实体。

一个使用 Spring Security 的 ReactiveSecurityContextHolderReactiveAuditorAware 的示例实现可能如下所示:

class ReactiveUsernameAuditor implements ReactiveAuditorAware<String>{  

  	@Override  
  	public Mono<String> getCurrentAuditor() {  
    	  	return ReactiveSecurityContextHolder.getContext()  
              	  	.map(SecurityContext::getAuthentication)  
              	  	.map(Authentication::getPrincipal)  
              	  	.map(Object::toString);  
  	}
}

您需要两个步骤才能为存储模块启用响应式审计。首先,通过存储特定的 @EnableReactive…Auditing 注解启用响应式审计。其次,注册一个 ReactiveAuditorAware Bean。Cassandra、Elasticsearch、MongoDB、Neo4j 和 R2DBC 支持响应式审计。

以下配置片段显示了如何启用响应式审计。您可以在 Github 上找到 完整示例

@Configuration  
@EnableReactiveCassandraAuditing  
class ApplicationConfiguration {  
  
	@Bean  
	ReactiveAuditorAware<String> reactiveAuditorAware() {  
		return () -> Mono.just("the-static-auditor-name");  
	}  
}

请注意,该示例会产生一个名为 the-static-auditor-name 的静态审计员。使用派生自上下文的动态审计员需要额外的组件,例如 Spring Security 和 WebFlux。请注意,之前一些存储通过 @Enable…Auditing 启用了响应式审计的日期部分。随着 @EnableReactive…Auditing 的引入,情况不再如此,@Enable…Auditing 仅启用命令式审计。如果您需要响应式审计,请确保相应地调整您的配置。

响应式 Spring 表达式语言(“SpEL”)上下文扩展

SpEL 上下文扩展代表了一个 SPI,允许插入应用程序特定或库特定的扩展,这些扩展可以通过 SpEL 表达式在各种地方提供功能。上下文扩展可以持有应用程序特定的状态,公开领域特定的功能,或提供对与传入应用程序请求关联的上下文数据的访问。SpEL 上下文功能的典型示例是访问与已认证或匿名应用程序用户发出的 HTTP 请求关联的安全上下文。

与响应式审计类似,任何每个请求的上下文都需要从订阅者 Context 中检索。在此版本中,我们引入了 ReactiveEvaluationContextProvider,它是 EvaluationContextProvider 的响应式变体。ReactiveEvaluationContextProvider 允许延迟 EvaluationContext 检索。通过返回 Mono,上下文提供者可以访问订阅者 Context。响应式上下文扩展需要实现 ReactiveEvaluationContextExtension,以便它们可以参与延迟的上下文扩展解析。响应式扩展可以提取上下文数据并将其传递给实际提供 Spring Data SpEL 评估机制功能的 EvaluationContextExtension

请注意,响应式 SpEL 支持仅适用于响应式存储库查询方法。其他组件中的 SpEL 表达式(例如,MongoDB @Document 或 Cassandra @Table 名称中的集合名称)在不考虑响应式上下文扩展的情况下进行解析,因为该 API 无法访问订阅者 Context

SpEL 上下文扩展解析的细化

自 Spring Data 成立以来,如果查询包含至少一个 SpEL 表达式,它就会尝试在 SpEL 表达式处理之前解析所有已注册的 SpEL 上下文扩展。上下文解析可能很昂贵。此外,有时上下文可能不存在。请考虑以下查询方法:

@Query("SELECT * FROM person WHERE tenant = :#{tenantId}")
List<Person> findAllPeopleForTenant();

@Query("SELECT * FROM person WHERE tenant = :#{tenantId} or 1=?#{hasRole('ROLE_ADMIN') ? 1 : 0")
List<Person> findAllPeople();

两个查询方法都使用 SpEL 表达式,第二个查询方法引用了 Spring Security 的 SpEL 上下文扩展。当调用第一个方法(findAllPeopleForTenant)并且该方法仅考虑 tenantId 时,Spring Data 也会加载 Spring Security 的扩展。如果在安全上下文之外调用该方法,则方法调用会失败,因为 Spring Security 无法解析安全上下文。在 CommandLineRunnerApplicationEventListener 或生命周期方法(如 @PostConstruct)中调用是典型的没有安全上下文的场景。

class MyComponent {

  @Autowired PersonRepository repository;
  
  @PostConstruct
  public void postConstruct() {
    TenantIdHolder.setCurrentTenant(4711);

    // this method fails with an exception because it attempts to resolve
    // the security context although it's not required in for the actual query.
    repository.findAllPeopleForTenant(); 
  }
}

通过引入响应式 SpEL 上下文扩展,我们细化了 SPI,只解析实际需要的扩展。org.springframework.data.spel.ExpressionDependencies 对 SpEL 表达式进行静态分析,并收集符号(属性引用和方法调用)。通过 EvaluationContextProvider 获取 EvaluationContext 接受 ExpressionDependencies 来检查哪些扩展提供了这些符号,并且只加载能够满足依赖项要求的扩展。此更改对大多数应用程序来说应该是透明的,因为它仅涉及内部代码。请注意,依赖关系分析使得 SpEL 启用查询可以在例如 @PostConstruct 中运行,前提是查询仅引用可解析的上下文功能。不再尝试解析未参与查询的上下文扩展,这一更改可能会反映在您的应用程序中。此优化适用于命令式和响应式代码路径,并且应该会提高性能。

存储库指标

了解应用程序的运行情况对某些业务至关重要。持久层(尤其是)可能是一个性能问题的罪魁祸首,这可能是由于网络或服务器端的慢查询执行。存储库指标现在为您提供了数据访问层运行时性能的真实来源,通过直接挂钩到 Spring Data 执行基础架构。附加到存储库的监听器会收到有关接口的每次调用的通知,提供有关存储库本身、调用的方法、传递的参数以及执行结果(成功、错误和取消信号)的信息,但不包括实际结果。

对于同步存储库接口,测量从查询方法调用开始,这包括实际解析潜在的注解查询以及评估通过 SpEL 表达式提供值的扩展(如前一节所述)所需的时间,并在返回潜在的已转换结果时结束。

对于响应式存储库,实际的订阅标记着调用的入口点,而完成或取消信号则完成了测量。

目前,设置过程不是最方便的,因为它需要一个 BeanPostProcessor 来操作存储库工厂 Bean,从而添加调用监听器。然而,未来的 Spring Boot 版本将为执行器端点中包含的存储库指标提供专用支持。

class RepositoryMetricsPostProcessor implements BeanPostProcessor { 
   public Object postProcessBeforeInitialization(Object bean, String beanName) {

     if (bean instanceof RepositoryFactoryBeanSupport) {
         RepositoryFactoryBeanSupport<?, ?, ?> repositoryFactoryBean = (...) bean;      
         repositoryFactoryBean.addRepositoryFactoryCustomizer(repositoryFactory -> {
         	repositoryFactory.addInvocationListener(System.out::println);
         });
     }
   return bean;
   }
}

为了让您能够看到它的实际运行效果,我们为您准备了一个小小的 指标示例。它会将调用持续时间打印到控制台。

PersonRepository.save(Object): 2 ms – SUCCESS

PersonRepository.save(Object): 2 ms – SUCCESS

PersonRepository.findAll(): 32 ms – SUCCESS

PersonRespository.findByName(String): 1 ms - SUCCESS

GraalVM 原始镜像

在谈论指标和性能时,人们可能会想到零扩展场景、启动速度和 GraalVM 原始镜像。Spring Data 团队在这方面付出了巨大的努力,从而改善了在使用 Spring Data 存储库编译原始镜像时的开发者体验。这些努力包括禁用某些功能的简单开关,例如用于优化实体实例化的代码生成、编译提示,以及作为 spring-graalvm-native 项目一部分的 SpringDataComponentProcessor。组件处理器会检查 Spring Data 存储库、域类型和方法签名。基于这些信息,它会将所有必需的代理、资源和反射配置添加到原始镜像中,使其能够正常工作。这还包括命令式和响应式接口的任何存储特定注解和自定义实现。如果您好奇,可以尝试一下!

至此,我们完成了本系列的第一部分。请关注您最喜欢的存储的相关更新,并查看 Spring Data 示例,以缩短其发布前的等待时间。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有