We now support exists and count projections derived from query methods and on Template API level.
Exists queries fetch rows limiting to a single row to optimize fetching.
Exists queries also support count results, i.e. a single row with a single numeric column is considered a count result. It's evaluated by comparing the number being greater to zero.
boolean exists = template.exists(Query.query(where("firstname").is("Walter")), Person.class);
long count = template.count(Query.query(where("lastname").is("White")), Person.class);
interface PersonRepository extends Repository<Person, String> {
long countByLastname(String lastname); // derived query
boolean existsByLastname(String lastname); // derived query
@CountQuery("SELECT COUNT(*) from users WHERE lastname = ?0")
long countQueryByLastname(String lastname); // String-based query
@ExistsQuery("SELECT * from users WHERE lastname = ?0 LIMIT 1")
boolean existsQueryByLastname(String lastname); // String-based query
}
Remove superfluous throws declarations.
We now support forward-only paging with Cassandra through the Template API and Repositories. Results in Cassandra are paged by navigating forward-only through pages described by a binary paging state encapsulated by CassandraPageRequest and accessible via the returned Slice. Spring Data Page's do not fit to Cassandra's paging concept because Cassandra paging is not based on limit/offset.
Page requests are applicable to a Query and as parameter of query methods.
Query query = Query.empty().pageRequest(CassandraPageRequest.first(10));
Slice<User> slice = template.slice(query, User.class);
do {
// consume slice
if (slice.hasNext()) {
slice = template.select(query, slice.nextPageable(), User.class);
} else {
break;
}
} while (!slice.getContent().isEmpty());
assertThat(ids).hasSize(100);
assertThat(iterations).isEqualTo(10);
interface UserRepository implements Repository<User, String> {
Slice<User> findAllByName(String name, Pageable pageRequest);
}
We now support Repository query methods with query options. Query options can be passed either as an additional parameter to a Repository query method or applied with annotation.
Annotation-based query options are supported via @Consistency. A query options parameter has precedence over the annotation if a method declares both, an annotation-based consistency level and accepts a query options parameter.
interface SampleRepository extends Repository<Person, String> {
@Query("SELECT * FROM person WHERE lastname = ?0;")
@Consistency(ConsistencyLevel.LOCAL_ONE)
Person findByLastname(String lastname);
@Consistency(ConsistencyLevel.LOCAL_ONE)
Person findByAge(int age);
Person findByAge(int age, QueryOptions options);
}
SampleRepository repository = …;
repository.findByAge(42, QueryOptions.builder().fetchSize(44).build());
We now create SASI (SSTable Attached Secondary Index) indexes during session initialization for properties annotated with @SASI. Properties can be annotated with analyzer-annotations to configure analyzers.
@Table
public class Person {
@Id String id;
@SASI String names;
@SASI @StandardAnalyzed(value = "de",
enableStemming = true, normalization = Normalization.UPPERCASE,
skipStopWords = true) String profession;
@SASI @NonTokenizingAnalyzed String country;
}
We now create secondary indexes for annotated properties via @Indexed. Index creation is part of schema creation that is executed after Session initialization and table creation.
We support plain secondary indexes and key/value/entry indexes for map columns. Index creation is useful for rapid development but should not be used in large setups or at least with care to not impact performance in a negative way.
@Table
public class Person {
@Id
private String key;
@Indexed("name_index")
private String name;
private Map<@Indexed String, String> keys;
}
Increase CassandraRepositoryConfigurationExtension visibility as this class is required by configuration infrastructure that configures Cassandra repository support such as Spring Boot.
Initialize MappingCassandraConverter with generic custom conversions and resolve package cycle between mapping and convert by initializing MappingCassandraConverter with generic custom conversions. Cassandra-specific custom conversions require external wiring via configuration.
Move IdInterfaceValidator to mapping package to resolve the final cycle between mapping and repository.support.
We now return WriteResult for insert(…) and update(…) methods accepting WriteOptions. Conditional writes return a state whether the operation was applied or not and this result is propagated via WriteResult to the caller.
Synchronous insert(…) and update(…) without WriteOptions methods do not return a value and are consistent with other Template API modules. The absence of an Exception indicates success. Asynchronous and reactive insert(…) and update(…) without WriteOptions continue to return the entity to make the entity accessible after operation completion.
Previously, we returned null if a lightweight transaction was not applied or suppressed the value value emission.
We now support lightweight transactions via InsertOptions and UpdateOptions. Options can be used with the imperative, asynchronous and reactive templates using insert(…) and update(…) write methods. Write methods do not return the entity if the operation is not applied because of a lightweight transaction. Specifically, the resulting entity is null using the imperative and asynchronous Cassandra templates.
The reactive Cassandra template suppresses particular entities (inserted/updated through a entity stream) if its write operation was not applied due to a lightweight transaction.
InsertOptions lwtInsertOptions = InsertOptions.builder().withIfNotExists().build();
User user = new User("heisenberg", "Walter", "White");
User inserted = template.insert(user, lwtInsertOptions);
UpdateOptions lwtUpdateOptions = UpdateOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
User updated = template.update(user, lwtUpdateOptions);
We now follow a more consistent naming scheme for cassandra repository interfaces.
* The basic, store-specific interface is now named CassandraRepository
* An extended variant, using MapId's is named MapIdCassandraRepository
That results in the following renames:
* CassandraRepository -> MapIdCassandraRepository
* TypedIdCassandraRepository -> CassandraRepository
TypedIdCassandraRepository was re-introduced as deprecated variant now extending CassandraRepository to preserve a majority of existing repository declarations.
We relocated packages of the former Spring CQL module and mapping/convert packages of the Spring Data Cassandra module:
org.springframework.cassandra -> org.springframework.data.cql
org.springframework.cassandra.core.cql.generator -> org.springframework.data.cql.core.generator
org.springframework.cassandra.core.cql -> org.springframework.data.cql.core
org.springframework.data.cassandra.convert -> org.springframework.data.cassandra.core.convert
org.springframework.data.cassandra.mapping -> org.springframework.data.cassandra.core.mapping