Spring Data 2023.0 (Ullman) Release Notes - spring-projects/spring-data-commons GitHub Wiki

General Themes

  • AOT reflection Querydsl Q classes and Kotlin types

  • Add keyset-based scrolling for MongoDB, Neo4j, and JPA modules

Participating Modules

Details

New and Noteworthy

Spring Data Commons - 3.1

Scroll API

Scrolling is a more fine-grained approach to iterate through larger results set chunks. Scrolling consists of a stable sort, a scroll type (Offset- or Keyset-based scrolling) and result limiting. You can define simple sorting expressions using property names and define static result limiting using the Top or First keyword through query derivation. You can concatenate expressions to collect multiple criteria into one expression.

Window<User> users = repository.findFirst10ByLastnameOrderByFirstname("Doe", ScrollPosition.offset());
do {

  for (User u : users) {
    // consume the user
  }

  // obtain the next Window
  users = repository.findFirst10ByLastnameOrderByFirstname("Doe", users.positionAt(users.size() - 1));
} while (!users.isEmpty() && users.hasNext());

An easier variant uses WindowIterator:

WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
  .startingAt(ScrollPosition.offset());

while (users.hasNext()) {
  User u = users.next();
  // consume the user
}

Scrolling can be used through query methods, Querydsl, and Query by Example executors through the Fluent Query API:

interface PersonRepository extends Repository<Person, Long>, QueryByExampleExecutor<Person> {	
}

repository.findBy(Example.of(…), q -> q.sortBy(…).scroll(ScrollPosition.offset()));

Scrolling supports two flavors:

  • Offset-based scrolling applying limit/offset in queries.

  • Keyset-based scrolling leveraging index support and sorting to retrieve sorted query sub-results

Spring Data JPA - 3.1

Spring Data JPA introduces HQL and JPQL parser!

One of Spring Data JPA’s handy features is letting users plugin in custom JPA queries through its @Query annotation. This isn’t too rigid because developers can still apply Sort parameters, giving end-users the ability to plugin various sort criteria. Moreover, Spring Data JPA supports paging, which requires the ability to count result sets.

interface SampleRepository extends CrudRepository<Employee, Long> {

    @Query("select e from Employee e where e.firstName = ?1")
    List<Employee> findCustomEmployees(String firstName, Sort sort);
}

Thus, we have allowed supported users by taking their @Query-provided queries and applied additional order by clauses and count() operations to support these modes of operations. Unfortunately, that support has been tricky the more complex custom queries become since for a long time, we only had a regular expression utility to support that.

The query above with a Sort.by("lastName") would turn into:

  select e
    from Employee e
   where e.firstName = ?1
order by e.lastName

With spring-data-jpa#2814, we have introduced a parser both for HQL (Hibernate Query Language) and spec-defined JPQL (JPA Query Language). Now, anytime you write a query, it is run through a proper parser, and it becomes much easier to curate the "final" query users need, whether that involves adding additional sort criteria to a query that already has an order by clause, or adding a new one should none exist.

We can also proper put the count() found in the right place to support Pageable queries that have a custom query.

What’s especially nice is that you don’t have to do anything. Spring Data JPA can detect whether Hibernate is on the classpath and switch between HQL or plain old JPQL.

Support for Keyset-based scrolling

As outlined in Spring Data Commons, Spring Data JPA provides support for the newly introduced Scroll API for derived query methods, Querydsl, and Query-by-Example usage. Stored procedures and String-based queries cannot be used for keyset-scrolling.

Spring Data Relational - 3.1

MariaDB is back among the supported databases for R2DBC

We mainly added the integration tests back to our code base. The important work was done in https://mvnrepository.com/artifact/org.mariadb/r2dbc-mariadb/1.1.3.

Embedded properties are now supported in Sort.by expressions

Therefore you can no use those for Pageable instances.

JdbcAggregateOperations now offers insertAll and updateAll.

Spring Data MongoDB - 4.1

Explicit Client-Side Field Level Encryption (CSFLE)

Explicit CSFLE uses the MongoDB driver’s optional encryption library (org.mongodb:mongodb-crypt) to perform encryption and decryption tasks during the mapping process of queries, updates and aggregations ($match stage). Other than automatic encryption, there is no need to provide a Schema with encryption information upfront.

The newly introduced @ExplicitEncrypted annotation makes sure to cypher values of annotated properties with a chosen encryption algorithm and Data Encryption Key (DEK) before sending them to the MongoDB server.

@EncryptedField(
  algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
  altKeyName = "secret-key"
)
String ssn;

Improved ReadPreference & ReadConcern handling

The new API now allows defining both, the ReadPreference, how the client routes an operation to members of a replica set and the ReadConcern, that controls read consistency and isolation level, for queries, aggregations and geo-near operations.

Support for let and pipeline in $lookup aggregation stage

The newly introduced options allow making use of MongoDB’s ability to perform correlated-subqueries which also removes the need for an equality match on the foreign fields.

Aggregation.lookup().from("restaurants")
  .localField("restaurant_name")
  .foreignField("name")
  .let(newVariable("orders_drink").forField("drink"))
  .pipeline(match(ctx -> new Document("$expr", new Document("$in", List.of("$$orders_drink", "$beverages")))))
  .as("matches");

Support for Keyset-based scrolling

As outlined in Spring Data Commons, Spring Data MongoDB provides support for the newly introduced Scroll API.

Index @Hint annotation

Query methods can be annotated with @Hint to provide an index hint to MongoDB.

@Hint("lastname-idx")
List<Person> findByLastname(String lastname);

@Query(value = …, hint = "lastname-idx")
List<Person> findByLastname(String lastname);

Using AggregationUpdate with bulk operations

We introduced method signatures accepting UpdateDefinition to apply Updates with Aggregation Pipelines through MongoDB’s Bulk Operation API. Signatures of updateOne and updateMulti accepting List<Pair<Query, Update>> are now updated to accept UpdateDefinition resulting in a method signature of List<Pair<Query, UpdateDefinition>> potentially requiring the adoption of code calling those methods when recompiling your code.

AggregationUpdate update = AggregationUpdate.update().set("average")
		.toValue(ArithmeticOperators.valueOf("tests").avg()).set("grade")
		.toValue(ConditionalOperators
				.switchCases(CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(90)).then("A"),
						CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(80)).then("B"),
						CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(70)).then("C"),
						CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(60)).then("D"))
				.defaultTo("F"));

bulkOperations.updateOne(where(query("firstname", "Joe")), update).execute();

Support for Reactive Bulk Operations

ReactiveMongoOperations now expose an operations interface for bulk operations with the reactive driver. Bulk operations emit entity callbacks for entities, e.g., when inserting or replacing objects.

ReactiveMongoOperations ops = …
AggregationUpdate update = …

Mono<BulkWriteResult> bulk = ops.bulkOps(BulkMode.ORDERED, Person.class)
    .insert(people)
    .update(where(query("firstname", "Joe")), update)
    .execute();

// subscribing to bulk will materialize the bulk operation and emit a BulkWriteResult.

Spring Data Neo4j - 7.1

Support for Keyset-based scrolling

As outlined in Spring Data Commons, Spring Data Neo4j provides support for the newly introduced Scroll API for derived query methods.

Stop using Neo4j’s internal id() function unless required by the domain model

Neo4j 5 introduced new and more versatile internal identifiers that work safely across multiple, shared Neo4j databases. They are called element ids and can be retrieved with the Cypher function elementId(n). In the process, the id(n) Cypher function got deprecated. The latter returned a long value identifying nodes and relationships.

Historically, SDN and previously SDN+OGM used this value for both internally tracking objects and as a strategy to fill @Id @GeneratedValue long id for entities. With SDN 7.1 SDN does not use id(n) internally anymore, but always elementId(n). It is still compatible with older Neo4j versions as the Cypher-DSL will translate the new function into a compatible call to the old function. Long values are still supported in your domain entities and business model, but be aware that their days are counted in the Neo4j database.

Therefor, you can now use @Id @GeneratedValue String id without an additional strategy. This will use the element id as a surrogate key. However, we do recommend external generators such as @GeneratedValue(generatorClass = GeneratedValue.UUIDGenerator.class) for UUID fields or @GeneratedValue(UUIDStringGenerator.class) for String fields.‘

import java.util.UUID;

import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.support.UUIDStringGenerator;

public interface AlternativesForLongIds {

	@Node
	class UsingElementIds {
		@Id
		@GeneratedValue
		String id;
	}

	@Node
	class UsingExternalGeneratedUUIDs {
		@Id
		@GeneratedValue(GeneratedValue.UUIDGenerator.class)
		UUID id;
	}

	@Node
	class UsingExternalGeneratedStringUUIDs {
		@Id
		@GeneratedValue(UUIDStringGenerator.class)
		String id;
	}
}

Spring Data Elasticsearch - 5.1

Spring Data Couchbase - 5.1

Spring Data for Apache Cassandra - 4.1

Spring Data Redis - 3.1

Spring Data KeyValue - 3.1

Spring Data REST - 4.1

Support for returning response bodies when deleting item resources — #2225

RepositoryRestConfiguration now exposes a ….setReturnResponseBodyOnDelete(…) to forcibly enable or disable a response body written when deleting item resources. The default (the option set to null) now considers the Accept header, similar to what we already do for POST and PUT requests. If present, a body will be returned alongside a status code of 200. If not, it will stay a 204.

Support for Slice as return type for methods backing query method resources — #2235

Repository methods returning a Slice are now properly turned into a Spring HATEOAS SlicedModel to then render the corresponding SliceMetadata and hypermedia elements to traverse the previous and next slice.

Support to receive aggregate references as request parameters — #2239

To translate URIs submitted as request parameters into Spring Data aggregate references, we now provide a (Association)AggregateReference type to be used as a handler method parameter. It allows the resolution of aggregate identifiers, the aggregate itself or a jMolecules Association.

Spring Data LDAP - 3.1

Release Dates

  • M1/M2 - Feb 17, 2023

  • M3 - Mar 20, 2023

  • RC1 - Apr 14, 2023

  • GA - May 12, 2023

  • OSS Support until: May 12, 2024

  • End of Life: September 12, 2025

⚠️ **GitHub.com Fallback** ⚠️