Introduce ReactiveResultSet.availableRows() to fetch rows without transparent paging. Refactor QueryUtils to extract a Slice from an Iterable. Add slice(Query, Class) to ReactiveCassandraOperations to expose a consistent API. Convert space indentation to tab indentation. Add tests. Add since tags. Add documentation. Reformat code.
Original pull request: #128.
We now provide Kotlin extensions for imperative, asynchronous, and reactive Template API interfaces.
CqlOperations and CassandraOperations expose Kotlin-friendly methods accepting KClasses and leveraging reified generics. We also provide Kotlin-friendly method renames for methods such as in that do not require quoting in Kotlin code.
operations.query(Person::class).inTable("my_table").asType<User>().all()
operations.select<Person>(query(where("firstname").isEqualTo("Walter") and where("lastname").isEqualTo("White")))
We now support Cassandra time columns via LocalTime types (JSR-310, Joda and ThreeTenBackport) in domain classes, queries and updates.
@Table
class Schedule {
@Id String id;
LocalTime scheduledAt;
}
Add AfterConvertEvent, introduce base class for AbstractDeleteEvent. Turn table name in mapping events to non-nullable. Replace guessTableName(…) with getTableName(…) and table name extraction from statements. Pass table name to converter mapper function for event propagation. Refactor tests to base class and test operations accessor.
Introduce lifecycle events and ProjectionFactory to AsyncCassandraTemplate.
Extend JavaDoc, add author and since tags. Reduce copyright year to inception year of new classes. Extend reference documentation.
Original pull request: #123.
We now support persistence lifecycle callbacks via Spring's ApplicationEvents. Events are fired upon select, insert, update, delete, and truncate statements. The following events are available:
* BeforeSaveEvent: Before inserting/updating a row in the database, via insert(…) and update(…).
* AfterSaveEvent: After inserting/updating a row in the database, via insert(…) and update(…).
* BeforeDeleteEvent: Before deleting row from the database, via delete(…) and truncate(…).
* AfterDeleteEvent: After deleting row from the database, via delete(…) and truncate(…).
* AfterLoadEvent: After retrieving a row from the database, via select(…), slice(…), and stream(…).
* AfterConvertEvent: After converting a row from the database to a POJO, via select(…), slice(…), and stream(…).
Original pull request: #123.
We now throw IncorrectResultSizeDataAccessException if a single entity query yields more result rows. Previously we gracefully returned the first element of the query without hinting whether the result was unique or not.
We now support map columns that use mapped user-defined types and converted, non-primitive types in their keys and values. Map columns can be used for schema generation, column and key/value types are derived from the declared types by inspecting whether they are either UDTs or they can be converted by a custom registered converter.
class Supplier {
@Id String id;
Map<Manufacturer, List<Currency>> currencies;
}
@UserDefinedType
class Manufacturer {
String name;
}
class UDTToCurrencyConverter implements Converter<UDTValue, Currency> {
// …
}
class CurrencyToUDTConverter implements Converter<Currency, UDTValue> {
// …
}
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);
}