Spring Data Cassandra

This document describes how to use Spring Data Cassandra and reactive Spring Data Cassandra with YCQL.

Spring Data Cassandra and YCQL

YCQL provides Cassandra wire-compatible query language for client applications to query the YugabyteDB database. YCQL is integrated with the Spring Data Cassandra project and supports POJO-based annotations, data templates, repositories, and so on.

The following is a non-exhaustive list of supported features:

  • Yugabyte Java driver for YCQL 3.x and 4.x drivers.
  • Automatic implementation of Repository interfaces including support for custom query methods.
  • Build repositories based on common Spring Data interfaces.
  • Synchronous, reactive, and asynchronous YCQL operations.

Project Dependencies

Spring Data Cassandra projects are bundled with the Apache Cassandra Java driver. To enhance performance, it is recommended to replace this driver with the Yugabyte Java driver for YCQL.

Yugabyte Java Driver YCQL 3.x

<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-cassandra</artifactId>
  <version>2.2.12.RELEASE</version>
  <exclusions>
<exclusion>
<groupId>com.datastax.cassandra</groupId>
<artifactId>cassandra-driver-core</artifactId>
</exclusion>
  </exclusions>
</dependency>

<!-- YCQL Driver -->
<dependency>
 <groupId>com.yugabyte</groupId>
 <artifactId>cassandra-driver-core</artifactId>
 <version>3.8.0-yb-6</version>
</dependency>

The following shows how to exclude and add dependencies via Gradle:

dependencies {
  compile('org.springframework.data:spring-data-cassandra:2.2.12.RELEASE') {
  exclude group: "com.datastax.cassandra", name: "cassandra-driver-core"
}
  compile('com.yugabyte:cassandra-driver-core:3.8.0-yb-6')
}

Yugabyte Java Driver YCQL 4.x

<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-cassandra</artifactId>
  <version>3.0.6.RELEASE</version>
  <exclusions>
<exclusion>
<groupId>com.datastax.oss</groupId>
<artifactId>java-driver-core</artifactId>
</exclusion>
  </exclusions>
</dependency>

<!-- YCQL Driver -->
<dependency>
 <groupId>com.yugabyte</groupId>
 <artifactId>java-driver-core</artifactId>
 <version>4.6.0-yb-6</version>
</dependency>

The following shows how to exclude and add dependencies via Gradle:

dependencies {
  compile('org.springframework.data:spring-data-cassandra:3.0.6.RELEASE') {
  exclude group: "com.datastax.oss", name: "java-driver-core"
}
  compile('com.yugabyte:java-driver-core:4.6.0-yb-6')
}

Yugabyte Java driver for YCQL provides support for single hop fetch which enables topology awareness, shard awareness data access using Spring Data Cassandra templates and repositories. In addition, Yugabyte Java drivers for YCQL support distributed transactions in Spring Boot applications using custom query methods.

Sample Spring Boot Project

A sample Spring boot project is available at https://github.com/yugabyte/spring-ycql-demo. The following steps demonstrate how to incrementally build a Spring boot application using Spring Data Cassandra and YCQL.

Use Spring Initializer

  1. Navigate to https://start.spring.io to create a new Spring boot project. Select the following dependencies for implementing the Spring boot application:

    • Spring Boot Data Cassandra
    • Java 8
  2. Update the Maven dependencies to use Yugabyte Java driver for YCQL, as follows:

      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-cassandra</artifactId>
        <exclusions>
    <exclusion>
    <groupId>com.datastax.oss</groupId>
    <artifactId>java-driver-core</artifactId>
    </exclusion>
        </exclusions>
      </dependency>
      <dependency>
        <groupId>com.yugabyte</groupId>
        <artifactId>java-driver-core</artifactId>
        <version>4.6.0-yb-6</version>
      </dependency>
    
  3. Configure the Spring boot application to connect to YugabyteDB cluster using the following properties in the application.properties file:

    Property Description
    spring.data.cassandra.keyspace-name YCQL keyspace
    spring.data.cassandra.contact-points YugabyteDB T-servers. Comma-separated list of IP addresses or DNS
    spring.data.cassandra.port YCQL port
    spring.data.cassandra.local-datacenter Datacenter that is considered "local". Contact points should be from this datacenter.

Set Up Domain Objects and Repositories for YCQL Tables

Create a Customer object to provide data access to allow Spring boot applications to manage first and last names of customers in a YugabyteDB table. To represent the object-table mapping to this data at the application level, you need to create a Customer Java class, as follows:

package com.yugabyte.example.ycqldataaccess;

public class Customer {

  private long id;
  private String firstName, lastName;

  public Customer(long id, String firstName, String lastName) {
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
  }

  @Override
  public String toString() {
    return String.format(
    "Customer[id=%d, firstName='%s', lastName='%s']",
    id, firstName, lastName);
  }

 // define getters and setters
 // ...
}

Store and Retrieve Data

Spring Data Cassandra provides the CassandraRepositories interface that removes all the boilerplate code and simplifies definition of CRUD operations against YugabyteDB tables. Most of the YugabyteDB connection handling, exception handling, and general error handling is performed by repositories, leaving you to implement the business logic.

Create the CustomerRepository interface, as follows:

package com.yugabyte.example.ycqldataaccess;

import java.util.Optional;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CustomerRepository extends CassandraRepository<Customer, String> {
  Optional<Customer> findByFirstName(String firstName);
}

The following YCQLDataAccessApplication.java is a demonstration of a class that can store and retrieve data from YugabyteDB tables using Cassandra repositories:

package com.yugabyte.example.ycqldataaccess;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
import com.datastax.oss.driver.api.core.CqlSession;

@SpringBootApplication
@EnableCassandraRepositories(basePackageClasses = Customer.class)
public class YcqlDataAccesssApplication implements CommandLineRunner {
  private static final Logger log = LoggerFactory.getLogger
                                    (YcqlDataAccesssApplication.class);
  @Autowired
  CustomerRepository customerRepository;

  @Autowired
  CqlSession ycqlSession;

  public static void main(String[] args) {
   SpringApplication.run(YcqlDataAccesssApplication.class, args);
  }

  @Override
  public void run(String... args) throws Exception {
   log.info("Creating Keyspace");
   ycqlSession.execute("CREATE KEYSPACE IF NOT EXISTS demo;");
   log.info("Creating table");
   ycqlSession.execute("CREATE TABLE demo.customer
      (\n" + "  id INT PRIMARY KEY,\n" + " firstName text,\n" \+
       " lastName text\n" + ") WITH default_time_to_live = 0\n"
      \+ " AND transactions = {'enabled': 'true'};");

    // Split the array of whole names into an array of first-last names
    List<Object[]> splitUpNames = Arrays.asList(
                    "Tim Bun", "Mike Dean", "Alan Row", "Josh Rambo")
                    .stream().map(name -> name.split(" ")).collect(Collectors.toList());

    // Use a Java 8 stream to print out each tuple of the list
   splitUpNames.forEach(name -> {
      int id = 1;
      log.info(String.format("Inserting customer record for %s %s", name[0], name[1]));
      customerRepository.save(new Customer(id, (String) name[0], (String) name[1]));
      id++;
   });
   log.info("Querying for customer records where first_name = 'Josh':");
   log.info(customerRepository.findByFirstName("Josh").toString());
  }
}

The CustomerRepository type is autowired in YcqlDataAccesssApplication which allows the insert of new records into the customer table using the customerRepository.save() method.

You can retrieve the data using the customerRepository.findByFirstName() method which returns records based on the firstName column. The implementation of the method is supplied by Spring at runtime.

Build an Executable JAR

The next step is to run the Spring application from the command line using Maven, as follows:

./mvnw spring-boot:run

Alternatively, you can build an uber JAR file by using the following command:

./mvnw clean package

Then you run the JAR file using the following command:

java -jar target/ycqldataaccess-1.0.0.jar

Expect output similar to the following:

INFO 28010 --- [main] c.y.e.y.YcqlDataAccesssApplication    : Started YcqlDataAccesssApplication in 1.7 seconds (JVM running for 2.139)

INFO 28010 --- [main] c.y.e.y.YcqlDataAccesssApplication    : Creating Keyspace

INFO 28010 --- [main] c.y.e.y.YcqlDataAccesssApplication    : Creating table

INFO 28010 --- [main] c.y.e.y.YcqlDataAccesssApplication    : Inserting customer record for Tim Bun

INFO 28010 --- [main] c.y.e.y.YcqlDataAccesssApplication    : Inserting customer record for Mike Dean

INFO 28010 --- [main] c.y.e.y.YcqlDataAccesssApplication    : Inserting customer record for Alan Row

INFO 28010 --- [main] c.y.e.y.YcqlDataAccesssApplication    : Inserting customer record for Josh Rambo

INFO 28010 --- [main] c.y.e.y.YcqlDataAccesssApplication    : Querying for customer records where first_name = 'Josh':

INFO 28010 --- [main] c.y.e.y.YcqlDataAccesssApplication    : Optional[Customer[id=1, firstName='Josh', lastName='Rambo']]

Spring Data Reactive Repositories and YCQL

YCQL API is compatible with Spring Data reactive repositories for Apache Cassandra.

Using Spring WebFlux and Spring Data reactive repositories, you can implement fully reactive Spring Microservices with YCQL API.

Project Dependencies

Reactive Spring Data Cassandra projects are bundled with the Apache Cassandra Java driver. To enhance performance, it is recommended to replace this driver with Yugabyte Java driver for YCQL, as follows:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-cassandra-reactive</artifactId>
  <exclusions>
    <exclusion>
        <groupId>com.datastax.oss</groupId>
        <artifactId>java-driver-core</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<dependency>
  <groupId>com.yugabyte</groupId>
  <artifactId>java-driver-core</artifactId>
  <version>4.6.0-yb-6</version>
</dependency>

Sample Spring Boot Project for Reactive

A sample Spring boot project is available at https://github.com/yugabyte/spring-reactive-ycql-client. The following steps show how to incrementally build a Spring boot application using Spring Data Cassandra and YCQL.

Use Spring Initializer

  1. Navigate to https://start.spring.io to create a new Spring boot project. Select the following dependencies for implementing the Spring boot application:

    • Spring Boot Data Cassandra Reactive
    • Java 8
  2. Update the Maven dependencies to use the Yugabyte Java driver for YCQL, as follows:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-cassandra-reactive</artifactId>
      <exclusions>
        <exclusion>
          <groupId>com.datastax.oss</groupId>
          <artifactId>java-driver-core</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    
    <dependency>
      <groupId>com.yugabyte</groupId>
      <artifactId>java-driver-core</artifactId>
      <version>4.6.0-yb-6</version>
    </dependency>
    
  3. Configure the Spring boot application to connect to YugabyteDB cluster using the following properties in the application.properties file:

    Property Description
    spring.data.cassandra.keyspace-name YCQL keyspace
    spring.data.cassandra.contact-points YugabyteDB T-servers. Comma-separated list of IP addresses or DNS
    spring.data.cassandra.port YCQL port
    spring.data.cassandra.local-datacenter Datacenter that is considered "local". Contact points should be from this datacenter.

Set Up Domain Objects and Repositories for YugabyteDB Tables

Create a Customer object to provide data access to allow Spring boot applications to manage first and last names of customers in a YugabyteDB table. To represent the object-table mapping to this data at the application level, you need to create a Customer Java class, as follows:

package com.yugabyte.example.ycqldataaccess;

public class Customer {

  private long id;
  private String firstName, lastName;

  public Customer(long id, String firstName, String lastName) {
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
  }

  @Override
  public String toString() {
    return String.format(
    "Customer[id=%d, firstName='%s', lastName='%s']",
    id, firstName, lastName);
  }

 // define getters and setters
 // ...
}

Store and Retrieve Data

Spring Data Cassandra Reactive provides the ReactiveCassandraRepositories interface that removes all the boilerplate code and simplifies definition of CRUD operations against YugabyteDB tables. Most of the YugabyteDB connection handling, exception handling, and general error handling is performed by repositories, leaving you to implement the business logic.

Create the CustomerReactiveRepository interface, as follows:

package com.yugabyte.example.ycqldataaccess;

import org.springframework.data.cassandra.repository.ReactiveCassandraRepository;
import reactor.core.publisher.Mono;

public interface CustomerReactiveRepository extends
                 ReactiveCassandraRepository<Customer, String> {
  Mono<Customer> findByFirstName(String firstName);
}

The following YCQLReactiveDataAccessApplication.java is a demonstration of a class that can store and retrieve data from YugabyteDB tables using Cassandra repositories:

package com.yugabyte.example.ycqldataaccess;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.cassandra.repository.config.EnableReactiveCassandraRepositories;
import com.datastax.oss.driver.api.core.CqlSession;

@SpringBootApplication
@EnableReactiveCassandraRepositories(
  basePackageClasses = CustomerReactiveRepository.class)
public class YcqlReactiveDataAccessApplication implements CommandLineRunner {
  private static final Logger log = LoggerFactory.getLogger(YcqlReactiveDataAccessApplication.class);

  @Autowired
  CustomerReactiveRepository customerReactiveRepository;

  @Autowired
  CqlSession ycqlSession;

  public static void main(String[] args) {
    SpringApplication.run(YcqlReactiveDataAccessApplication.class, args);
  }

  @Override
  public void run(String... args) throws Exception {
    log.info("Creating table");
    ycqlSession.execute("CREATE TABLE IF NOT EXISTS demo.customer (\n" +
                        " id INT PRIMARY KEY,\n"+ " firstName text,\n" +
                        " lastName text\n" + ") WITH default_time_to_live = 0\n" +
                        " AND transactions = {'enabled': 'true'};");

    // Split the array of whole names into an array of first/last names
    List<Object[]> splitUpNames =
      Arrays.asList("Tim Bun", "Mike Dean", "Alan Row", "Josh Rambo")
                    .stream().map(name -> name.split(" "))
                    .collect(Collectors.toList());

    // Use a Java 8 stream to print out each tuple of the list
    splitUpNames.forEach(name -> {
            int id = 1;
            log.info(String.format("Inserting customer record for %s %s",
                                   name[0], name[1]));
            customerReactiveRepository.save(new Customer(id, (String) name[0],
                                                             (String) name[1]));
            id++;
     });

     log.info("Querying for customer records where first_name = 'Josh':");
     // Using Reactive Repository for Querying the data.
     customerReactiveRepository.findByFirstName("Josh")
                               .doOnNext(customer -> log.info(customer.toString()))
                               .block();
  }
}

Compatibility Matrix

YugabyteDB Java Driver Spring Data Cassandra
3.8.0-yb-6 2.2.12.RELEASE and later
4.6.0-yb-6 3.0.6.RELEASE and later