Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreThe release announcement blog does a good job highlighting some of the many features in Spring Data 2024.1. Remember: Spring Data is an umbrella project, aggregating modules supporting, among other things, Couchbase, Redis, MongoDB, JDBC, R2DBC, Neo4J, Apache Cassandra, and countless other data stores. It’s the easiest way to connect your data stores to your applications. And indeed, we could write a small book with all the new features here!
Here are some of the features that caught my eye.
.jar
on the classpath, or indeed code in another package, contribute extensions to the Spring Data repository mechanism via the Spring.factories
service factory mechanism@TimeSeries
in Spring Data MongoDBCqlGenerator
in Spring Data CassandraJedisClientConfig
with the JedisClientConfigBuilderCustomizer
interface.I wanted to talk about one feature that could change things up for me: the culmination of value expressions, which is a fancy way of saying that it’s possible to parameterize queries in the Spring Data modules using either or both of Spring Expression Language (SpEL) or property placeholder resolution expressions.
The value expression stuff is based on a nice lower-level API you can use in your applications.
package com.example.bootiful_34.data;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.data.expression.ValueEvaluationContext;
import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.expression.ValueParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.test.context.DynamicPropertyRegistrar;
import org.springframework.test.context.DynamicPropertyRegistry;
@SpringBootTest
@Import(ValueExpressionDynamicPropertyRegistrar.class)
class ValueExpressionTest {
@Test
void test(@Autowired Environment environment) {
var configuration = (ValueParserConfiguration) SpelExpressionParser::new;
var context = ValueEvaluationContext.of(environment, new StandardEvaluationContext());
var parser = ValueExpressionParser.create(configuration);
var expression = parser.parse("${message}-#{ (6 * 7) + ''}");
var result = expression.evaluate(context);
Assertions.assertEquals("ni hao-42", result);
}
}
@Configuration
class ValueExpressionDynamicPropertyRegistrar implements DynamicPropertyRegistrar {
@Override
public void accept(DynamicPropertyRegistry registry) {
registry.add("message", () -> "ni hao");
}
}
In this example, I’m using the low-level ValueEvaluationContext
in a test where I’ve dynamically contributed a property: message
with a greeting ni hao
. The test evaluates a String
that, in turn, has a property placeholder ${message}
and a SpEL expression #{ (6 * 7) + ''}
, in which we multiply two numbers and then turn the resulting expression into a String.
The result is, as you’d expect: ni hao-42
.
Now that you can see the possibilities let’s examine some of them in the Spring Data JDBC repository’s definitions of query methods.
package com.example.bootiful_34.data;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.ListCrudRepository;
import java.util.Collection;
interface CustomerRepository extends ListCrudRepository<Customer, Integer> {
@Query(" select * from #{ #tableName }")
Collection<Customer> all();
@Query(" select * from customer where language = :#{locale.language} ")
Collection<Customer> findCustomersBySystemLanguage();
// this is new! support for property placeholder resolution in queries!
@Query("select * from customer where os = :${os.name} ")
Collection<Customer> findCustomersHavingSameOperatingSystemAsUs();
}
The first query relies upon a SpEL context that furnishes the table name of the entity referenced in the query method (in this case, Customer
belongs to the customer
SQL database).
In the second example query, we match against a custom context contributed via EvaluationContextExtension
. This extension simply makes the user’s Locale
available during SpEL execution.
💡 TIP
Did you know about LocaleContextHolder
? It’s been in Spring since Spring Framework 1.2! It makes the current user’s Locale
object available. So, imagine an HTTP request comes in; Spring MVC determines the user’s Locale
and then installs it here. And you can read it any time you want as long as you’re in the same thread as the request. Nice.
package com.example.bootiful_34.data;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.data.spel.spi.EvaluationContextExtension;
import org.springframework.stereotype.Component;
import java.util.Locale;
@Component
class LocaleEvaluationContextExtension implements EvaluationContextExtension {
@Override
public String getExtensionId() {
return "locale";
}
@Override
public Locale getRootObject() {
return LocaleContextHolder.getLocale();
}
}
In the third example query, we’re matching a System property os.name
, which returns the user’s current operating system (Mac OS X, Unix-like, Windows).