Cassandra-specific Query Methods

This chapter explains Cassandra-specific query methods. This documentation uses imperative types. By using reactive return types, the same semantics apply to reactive repositories as well.

Most of the data access operations you usually trigger on a repository result in a query being executed against the Apache Cassandra database. Defining such a query is a matter of declaring a method on the repository interface. The following example shows a number of such method declarations:

PersonRepository with query methods
  • Imperative

  • Reactive

interface PersonRepository extends CrudRepository<Person, String> {

    List<Person> findByLastname(String lastname);                           (1)

    Slice<Person> findByFirstname(String firstname, Pageable pageRequest);  (2)

    Window<Person> findByFirstname(String firstname, CassandraScrollPosition pos, Limit limit);  (3)

    List<Person> findByFirstname(String firstname, QueryOptions opts);      (4)

    List<Person> findByFirstname(String firstname, Sort sort);              (5)

    List<Person> findByFirstname(String firstname, Limit limit);            (6)

    Person findByShippingAddress(Address address);                          (7)

    Person findFirstByShippingAddress(Address address);                     (8)

    Stream<Person> findAllBy();                                             (9)

    @AllowFiltering
    List<Person> findAllByAge(int age);                                     (10)
}
1 The method shows a query for all people with the given lastname. The query is derived from parsing the method name for constraints, which can be concatenated with And. Thus, the method name results in a query expression of SELECT * FROM person WHERE lastname = 'lastname'.
2 Applies pagination to a query. You can equip your method signature with a Pageable parameter and let the method return a Slice instance, and we automatically page the query accordingly.
3 Applies scrolling to a query. Scrolling wraps Cassandra’s PagingState into CassandraScrollPosition and allows dynamic limiting. You can also use findTop… for a static limit.
4 Passing a QueryOptions object applies the query options to the resulting query before its execution.
5 Applies dynamic sorting to a query. You can add a Sort parameter to your method signature, and Spring Data automatically applies ordering to the query.
6 Applies dynamic result limiting to a query. Query results can be limited using SELECT … LIMIT.
7 Shows that you can query based on properties that are not a primitive type by using Converter instances registered in CustomConversions. Throws IncorrectResultSizeDataAccessException if more than one match is found.
8 Uses the First keyword to restrict the query to only the first result. Unlike the preceding method, this method does not throw an exception if more than one match is found.
9 Uses a Java 8 Stream to read and convert individual elements while iterating the stream.
10 Shows a query method annotated with @AllowFiltering, to allow server-side filtering.
interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Long> {

  Flux<Person> findByFirstname(String firstname);                                (1)

  Flux<Person> findByFirstname(Publisher<String> firstname);                     (2)

  Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);    (3)

  Mono<Person> findFirstByFirstname(String firstname);                           (4)

  @AllowFiltering
  Flux<Person> findByAge(int age);                                               (5)
}
1 A query for all people with the given firstname. The query is derived by parsing the method name for constraints, which can be concatenated with And and Or. Thus, the method name results in a query expression of SELECT * FROM person WHERE firstname = :firstname.
2 A query for all people with the given firstname once the firstname is emitted from the given Publisher.
3 Find a single entity for the given criteria. Completes with IncorrectResultSizeDataAccessException on non-unique results.
4 Unlike the preceding query, the first entity is always emitted even if the query yields more result rows.
5 A query method annotated with @AllowFiltering, which allows server-side filtering.
Querying non-primary key properties requires secondary indexes.

The following table shows short examples of the keywords that you can use in query methods:

Table 1. Supported keywords for query methods
Keyword Sample Logical result

After

findByBirthdateAfter(Date date)

birthdate > date

GreaterThan

findByAgeGreaterThan(int age)

age > age

GreaterThanEqual

findByAgeGreaterThanEqual(int age)

age >= age

Before

findByBirthdateBefore(Date date)

birthdate < date

LessThan

findByAgeLessThan(int age)

age < age

LessThanEqual

findByAgeLessThanEqual(int age)

age ⇐ age

Between

findByAgeBetween(int from, int to) and findByAgeBetween(Range<Integer> range)

age > from AND age < to and lower / upper bounds (> / >= & < / ) according to Range

In

findByAgeIn(Collection ages)

age IN (ages…​)

Like, StartingWith, EndingWith

findByFirstnameLike(String name)

firstname LIKE (name as like expression)

Containing on String

findByFirstnameContaining(String name)

firstname LIKE (name as like expression)

Containing on Collection

findByAddressesContaining(Address address)

addresses CONTAINING address

(No keyword)

findByFirstname(String name)

firstname = name

IsTrue, True

findByActiveIsTrue()

active = true

IsFalse, False

findByActiveIsFalse()

active = false

Repository Delete Queries

The keywords in the preceding table can be used in conjunction with delete…By to create queries that delete matching documents.

  • Imperative

  • Reactive

interface PersonRepository extends Repository<Person, String> {

  void deleteWithoutResultByLastname(String lastname);

  boolean deleteByLastname(String lastname);
}
interface PersonRepository extends Repository<Person, String> {

  Mono<Void> deleteWithoutResultByLastname(String lastname);

  Mono<Boolean> deleteByLastname(String lastname);
}

Delete queries return whether the query was applied or terminate without returning a value using void.

Query Options

You can specify query options for query methods by passing a QueryOptions object. The options apply to the query before the actual query execution. QueryOptions is treated as a non-query parameter and is not considered to be a query parameter value. Query options apply to derived and string @Query repository methods.

To statically set the consistency level, use the @Consistency annotation on query methods. The declared consistency level is applied to the query each time it is executed. The following example sets the consistency level to ConsistencyLevel.LOCAL_ONE:

  • Imperative

  • Reactive

interface PersonRepository extends CrudRepository<Person, String> {

    @Consistency(ConsistencyLevel.LOCAL_ONE)
    List<Person> findByLastname(String lastname);

    List<Person> findByFirstname(String firstname, QueryOptions options);
}
interface PersonRepository extends ReactiveCrudRepository<Person, String> {

    @Consistency(ConsistencyLevel.LOCAL_ONE)
    Flux<Person> findByLastname(String lastname);

    Flux<Person> findByFirstname(String firstname, QueryOptions options);
}

The DataStax Cassandra documentation includes a good discussion of the available consistency levels.

You can control fetch size, consistency level, and retry policy defaults by configuring the following parameters on the CQL API instances: CqlTemplate, AsyncCqlTemplate, and ReactiveCqlTemplate. Defaults apply if the particular query option is not set.