Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreSpring Data 2020.0, based on Spring Framework 5.3, is out and with it, a ton of new features across the various stores, which are covered by the individual modules. While posting highlights along with the milestone announcements, we wanted to give you a more detailed description of the new features in a series of blog posts. These posts will cover, among other things:
@Bean
row mappers for the JDBC module.In this first part, we take the time to cover some general topics and certain aspects of the commons module that have effect on the store-specific implementations.
We start with the most obvious change: The versioning scheme.
After following a lexical ordering (honoring famous computer scientists) for more than 7 years, we switched to a CalVer-based versioning scheme.
This will mainly affect those of you who manage versions of Spring Data artifacts through the spring-data-releasetrain
artifact, which changed to spring-data-bom
and the new scheme.
org.springframework.data
spring-data-bom
2020.0.0
The individual modules continue to use the numeric versioning scheme.
Despite those infrastructural changes, the current release has a clear focus on extending the reactive story, more application insight, metrics, and a better developer experience when working with GraalVM native images. Still, we took the time to add some bits and pieces here and there, to hopefully make your everyday work a little more convenient.
One of those enhancements was to add support for java.lang.Optional
within projections. It is not a game changer, but it nevertheless lets if you want to avoid null values.
interface User {
String getNickname();
Optional<byte[]> getPicture();
}
Working on performance insights (more on that a little later), we now delay repository initialization till its first use, which speeds up application startup times.
We also took the time to Delombok the entire production codebase for improved readability and debugging.
This release contributes another important piece on its path to completing the reactive story. The addition of reactive context access let us work on features such as reactive auditing and SpEL context extensions evaluation inside a reactive flow. Till now, both Auditing and SpEL were, to a certain degree, already possible. The missing piece was the ability to access contextual details, such as an authentication principal that is typically provided by Spring Security in WebFlux arrangements.
When it comes to context, you have two options:
ThreadLocal
)The imperative variants of auditing and SpEL context extensions rely on ThreadLocal
storage. However, subscribing to a reactive pipeline removes any assumptions on which threads the pipeline is going to materialize. Therefore, this approach becomes unusable. This is where the second option comes into play, passing contextual details along with the invocation. Project Reactor's Context
feature allows attaching contextual data to a subscription. To access the context, an API is required to use Publisher
types. With this release, we therefore introduced reactive API variants for auditing with ReactiveAuditorAware
and ReactiveEvaluationContextExtension
.
interface ReactiveAuditorAware<T> {
Mono<T> getCurrentAuditor();
}
interface ReactiveEvaluationContextExtension extends ExtensionIdAware {
Mono<? extends EvaluationContextExtension> getExtension();
}
You can expose implementations of both interfaces as Spring beans. Spring Data picks them up and uses them for reactive auditing, respective of context extension.
Reactive Auditing associates auditable entities with a timestamp and the current user ("auditor"). Properties annotated with @CreatedBy
or @LastModifiedDate
are populated by Spring Data upon insert and update. In previous versions, properties annotated with @CreatedDate
and @LastModifiedDate
were updated through entity callbacks. As of this release, the auditor is propagated from ReactiveAuditorAware
into the entity by using a dedicated entity callback.
An example implementation for ReactiveAuditorAware
that uses Spring Security's ReactiveSecurityContextHolder
could look like:
class ReactiveUsernameAuditor implements ReactiveAuditorAware<String>{
@Override
public Mono<String> getCurrentAuditor() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Authentication::getPrincipal)
.map(Object::toString);
}
}
You need two steps to enable reactive auditing for your store module. First, enable reactive auditing through your store-specific @EnableReactive…Auditing
annotation. Second, register a ReactiveAuditorAware
bean. Reactive auditing is supported for Cassandra, Elasticsearch, MongoDB, Neo4j, and R2DBC.
The following configuration snippet shows how to enable reactive auditing. You can find the full example on Github.
@Configuration
@EnableReactiveCassandraAuditing
class ApplicationConfiguration {
@Bean
ReactiveAuditorAware<String> reactiveAuditorAware() {
return () -> Mono.just("the-static-auditor-name");
}
}
Note that the example results in a static auditor that is named the-static-auditor-name
. Using a dynamic auditor derived from the context requires additional components, such as Spring Security and WebFlux. Note that several stores previously enabled the date-part of reactive auditing through @Enable…Auditing
. With the introduction of @EnableReactive…Auditing
, that is no longer the case, and @Enable…Auditing
enables solely imperative auditing. Make sure to adopt your configuration accordingly if you require reactive auditing.
SpEL Context extensions represent an SPI to allow plugging in application- or library-specific extensions that provide functionality that can be consumed in various places by using SpEL expressions. Context extensions can hold application-specific state, expose domain-specific functionality, or give access to contextual data associated with an incoming application request. A typical example for a SpEL Context feature is accessing the security context that is associated with a HTTP request issued by an authenticated or anonymous application user.
Similar to reactive auditing, any per-request context needs to be retrieved from the subscriber Context
. In this release, we introduced ReactiveEvaluationContextProvider
, which is a reactive variant of EvaluationContextProvider
. ReactiveEvaluationContextProvider
allows deferred EvaluationContext
retrieval. By returning Mono
, the context provider can access the subscriber Context
. Reactive context extensions need to implement ReactiveEvaluationContextExtension
so that they can participate in deferred context extension resolution. Reactive extensions can extract contextual data and pass it on to the actual EvaluationContextExtension
that provides functionality to Spring Data's SpEL evaluation mechanism.
Note that Reactive SpEL support is available only for reactive repository query methods. SpEL expressions in other components (for example, a collection name in a MongoDB @Document
or a Cassandra @Table
name) are resolved without considering reactive context extensions as that API has no access to the subscriber Context
.
Since its inception, Spring Data has attempted resolution of all registered SpEL context extensions prior to SpEL expression processing if a query contained at least one SpEL expression. Context resolution can be expensive. Also, sometimes, context may be not present. Consider the following query methods:
@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();
Both query methods use SpEL expressions, and the second query method has a reference to Spring Security's SpEL context extension. When calling the first method (findAllPeopleForTenant
) that only considers the tenantId
, Spring Data also loaded Spring Security's extension. If the method was called outside of a security context, the method invocation failed because Spring Security could not resolve a security context. Invocation in a CommandLineRunner
, ApplicationEventLister
, or lifecyle method (such as @PostConstruct
) are typical examples where no security context is given:
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();
}
}
With the introduction of Reactive SpEL Context extensions, we refined our SPI to resolve only extensions that are actually needed. org.springframework.data.spel.ExpressionDependencies
performs a static analysis of the SpEL expression and collects symbols (property references and method calls). Obtaining the EvaluationContext
through EvaluationContextProvider
accepts ExpressionDependencies
to inspect which extensions provide these symbols and load only the ones that can satisfy the dependency requirements. This change should be transparent to most applications, as it touches only internal code.
Note that the dependency analysis enables arrangements of running SpEL-enabled queries in, for example, @PostConstruct
, if the query refers only to contextual functionality that can be resolved. Context extensions that are not involved in the query are no longer attempted for resolution, and that change may reflect into your application. That optimization applies to both imperative and reactive code paths and should result in improved performance.
Having insight into the application as it runs is crucial to some businesses. The persistence layer (especially) can be, perhaps due to the network or slow query execution on server side, a culprit in performance issues. Repository metrics now give you a source of truth for runtime performance of your data access layer, by hooking directly into the Spring Data execution infrastructure. Listeners attached to the repository get notified about each and every invocation done to the interface, providing information about the repository itself, the method invoked, the arguments passed on, and the execution outcome (success, error, and cancelation signals), omitting the actual result.
For synchronous repository interfaces, the measurement starts at query method invocation time, which includes the time taken to actually parse a potential annotated query and evaluate extensions providing values through SpEL expressions, as outlined in the preceding section, and ends when the potentially converted result is returned.
For reactive repositories, the actual subscription marks the invocation entry point, whereas the complete or cancel signal completes the measurement.
At this time, the setup is not the most convenient, as it requires a BeanPostProcessor
to manipulate the repository factory bean by adding the invocation listeners. However, future Spring Boot releases will offer dedicated support for repository metrics included in the actuator endpoints.
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;
}
}
So that you can see it in action, we have prepared a little metrics example for you. That prints invocation durations to the console.
PersonRepository.save(Object): 2 ms – SUCCESS
PersonRepository.save(Object): 2 ms – SUCCESS
PersonRepository.findAll(): 32 ms – SUCCESS
PersonRespository.findByName(String): 1 ms - SUCCESS
When talking about metrics and performance, one might soon think of scale-to-zero scenarios, startup speed, and GraalVM native images. The Spring Data team has been pushing hard on that end, resulting in an improved developer experience when compiling native images that make use of Spring Data repositories.
The efforts cover simple toggles to disable certain features, such as the code generation for an optimized entity instantiation scenario, compilation hints, and a SpringDataComponentProcessor
as part of the spring-graalvm-native project.
The component processor inspects Spring Data repositories, the domain types, and method signatures. Based on that information, it adds all the required proxy, resource, and reflection configuration to the native image to make it work.
This also includes any store-specific annotations and custom implementations for both the imperative and reactive interfaces.
If you are curious, give it a try!
With that, we finish our first part of this series. Watch this space for updates related to your favorite store and check out the Spring Data Examples to shorten the time till it is published.