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 allow query option mutation through QueryOptions.mutate() returning an initialized builder. The mutation builder is initialized with the state of the QueryOptions object and allows further customization without changing the previous state of the immutable QueryOptions object.
QueryOptions queryOptions = …;
QueryOptions mutated = queryOptions.mutate().readTimeout(Duration.ofSeconds(5)).build();
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());
Expose createInsert(…) in the synchronous and reactive repository base classes to simplify base class extension. Derived classes are no longer required to implement createInsert(…) themselves but can reuse the existing methods. Extract createInsert(…) body to InsertUtil.
Add tests that verify Java class to Cassandra type mapping for simple types that are supported natively by the driver.
Related tickets: DATACASS-271, DATACASS-280, DATACASS-375.
We map varchar and text types explicitly to data types to ensure resolution to the appropriate data type if queried by name. The driver returns only text via DataType.allPrimitiveTypes() because text and varchar are aliases and allPrimitiveTypes returns a set.
Remove indirection via MultiLevelSetFlattenerFactoryBean and create a KeyspaceActions wrapper that encapsulates the actual actions. Introduce KeyspaceActionSpecificationFactory for keyspace action creation.
Encapsulate required KeyspaceSpecification properties in immutable base classes. Introduce static factory methods to create value objects where possible. Rewrite Javadoc to reflect the nature of configuration objects and not builders since these objects to not build a target object. Refactor duplicate code into utility classes.
Mark all packages with Spring Frameworks @NonNullApi. Add Spring's @Nullable to methods, parameters and fields that take or produce null values. Adapted using code to make sure the IDE can evaluate the null flow properly. Fix Javadoc in places where an invalid null handling policy was advertised. Strengthened null requirements for types that expose null-instances.
Encapsulate KeyspaceIdentifier and CqlIdentifier with static factory methods to avoid temporary null state of fields.
Require non-null QueryOptions and provide empty option instances. Introduce methods returning non-null values (getRequired…()) for code paths known to operate on values that are available.
We now return the domain type via CassandraQueryMethod.getEntityInformation() for query derivation. Previously, interface types were returned and they were used as input type for query derivation. Query methods referencing domain type properties that do not exist on the projection type caused PropertyReferenceException.
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;
}
We no longer expose selectBySimpleIds(…) via CassandraOperations. selectBySimpleIds was a limited short-cut method that create a SELECT statement using the primary key name and using Id values as-is without applying type conversions. The replacement is to use a select(…) method accepting Query and an appropriate query:
select(query(where("id")).in("key", "other-key"), Person.class)
assuming the primary key property is id.
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.