-
Notifications
You must be signed in to change notification settings - Fork 675
Spring Data 2023.1 (Vaughan) Release Notes
-
Virtual Thread usage through
Executor
configuration -
Java 21 Compatibility
-
Support for Kotlin Value Classes
-
Explore optimizations for Checkpoint/Restore
-
Single Query Loading for Spring Data JDBC
-
Migrate documentation to Antora
Details
-
Spring Data Build - 3.2
Kotlin Value Classes are a language feature that allows creating a wrapper class around single values with reduced heap allocations. Value classes are inlined on the JVM. Inlining results in flattening the value type into the property declaration site. Usage of Value classes imposes name mangling, introduces specific behavior to Copy methods, and how constructors are generated by Kotlin.
Spring Data now can:
-
Instantiate classes that define properties using Kotlin Value Classes
-
Retrieve and set properties using getters/setters and the
copy
method (for Data classes)
Value classes such as the following example can now be used for persistence operations:
@JvmInline
value class Email(val email : String)
data class Person(@Id val id: String, val email : Email)
Note that Kotlin inlining rules can require Value boxing type usage if the component is a primitive or if values use inner nullability. This results in the compiled class using the Value class as the property type instead of the Value type. Stores such as MongoDB will represent such models using subdocuments, so you want to watch out for type nullability to avoid type changes, especially for existing data.
Limiting result sizes worked in the past by either using the Top…
/First…
keywords (as in findTop10By
) or PageRequest
when using pagination. With the recent introduction of ScrollPosition
, PageRequest
isn’t applicable and a static limit is sometimes not what your use case requires. Spring Data 3.2 ships a Limit
type to specify the number of results to be returned dynamically:
interface UserRepository {
List<User> findByLastname(String lastname, Limit limit);
}
repository.findByLastname("White", Limit.of(10));
repository.findByLastname("White", Limit.unlimited());
When working with SQL databases, the schema is an essential part. Spring Data JDBC supports a wide range of schema options yet when starting with a domain model it can take time to come up with an initial domain model. To help you with a code-first approach, Spring Data JDBC ships with an integration to create database changesets using Liquibase.
LiquibaseChangeSetWriter
is the core class to create change sets. The writer can operate in two modes:
-
Initial Schema Creation (without an existing database)
-
Differential Schema Migration (against a database connected via JDBC).
Consider the following example:
H2Database h2Database = new H2Database();
h2Database.setConnection(new JdbcConnection(c));
File changelogYml = new File(new File("my/directory"), "changelog.yml");
LiquibaseChangeSetWriter writer = new LiquibaseChangeSetWriter(relationalMappingContext);
writer.writeChangeSet(new FileSystemResource(changelogYml));
LiquibaseChangeSetWriter
inspects all known entities in the RelationalMappingContext
and writes a changeset to an existing (or new) changelog file.
Mapping annotations for the table and column name respective mapped collections now accept SpEL expressions to determine table and column names at runtime using expressions.
@Table("#{myTenantController.getPersonTableName()}")
static class Person {
@Id
@Column("#{myTenantController.getIdentifierColumnName()}") Long id;
}
Expression evaluation leverages Spring Data’s EvaluationContextExtension
mechanism in which extension beans can contribute SpEL functionality. Note that expression results are used as table/column names. These are sanitized through a default SqlIdentifierSanitizer.words()
, allowing characters and underscores to limit impact of unwanted SQL characters. A different sanitizer can be configured through RelationalMappingContext
.
You can now use Single Query Loading to fetch entire entity graphs with a single query avoiding the N+1 loading problem. Single Query Loading is significantly more efficient, especially for complex aggregates consisting of many entities, as it uses a Single Query to materialize results.
Currently, this feature is restricted according to the following rules:
-
It works for aggregates that reference a single entity collection only. As we advance, we want to remove this constraint.
-
The aggregate must not use
AggregateReference
or embedded entities. Will be removed in the future. -
The database dialect, specifically the underlying database, must support analytic functions (windowing). All databases except H2 and HSQL support the required analytic functions.
-
As of M2, only
CrudRepository.find
methods are supported as a milestone preview feature. We plan to extend Single Query Loading for derived queries and queries usingQuery
. With the evolution towards filter specifications, we will also add support for annotated query methods. -
Single Query Loading must be enabled via
JdbcMappingContext.setSingleQueryLoadingEnabled(true)
.
If you are interested in further progress and plans for this feature, please follow https://github.com/spring-projects/spring-data-relational/issues/1445
Note
|
|
Note
|
|
Spring Data Cassandra provides now a CassandraScrollPosition
to leverage scrolling queries returning Window<T>
. Scrolling is a much more natural fit than pagination through Slice
. It allows you also to use WindowIterator
to use Cassandra’s fetch size (pagination) to scroll across large results:
WindowIterator<Person> iterator = WindowIterator
.of(scrollPosition -> personRepository.findAllWindowByLastname("White", scrollPosition, Limit.of(2)))
.startingAt(CassandraScrollPosition.initial());
With Java 21 introducing SequencedCollection
, we had to revisit our RedisList
as it implements List
and Deque
. Both types return a reversed view of their underlying collection, however since Java doesn’t provide a union type combining List
and Deque
, we moved ahead and made our RedisList
compatible with the newly introduced library functionality. You can use the reversed view of RedisList
with Java 17 already. On Java 21, the RedisList.reversed()
method matches the SequencedCollection.reversed()
signature.
RedisList<Person> list = …;
list.reversed().stream(); // consume the underlying list with reversed semantics
For our efforts on Checkpoint/Restore, we refactored Jedis and Lettuce RedisConnectionFactory
to Lifecycle
beans. Connection factories can be started, stopped, and restarted. By default, connection factories are started upon initialization (i.e. afterPropertiesSet
) to retain existing functionality. Connection factories can be stopped to shut down connections and connection pools to take a snapshot and restarted when starting from a checkpoint snapshot.
-
M1 - Jul 14, 2023
-
M2 - Aug 18, 2023
-
M3 - Sept 15, 2023
-
RC1 - Oct 13, 2023
-
GA - Nov 17, 2023
-
OSS Support until: Nov 17, 2024
-
End of Life: Feb 17 2025