You are here
Home > Cassandra >

Spring Boot Cassandra CRUD Examples

Spring Data Cassandra vs Spring Data JPAIn continuation to Cassandra DB installation, now in this article we will learn the most important DB operations. If we are in Software development world, we should at least know ‘How to write CRUD operation on the database front’. Needless to say, how much importance these operations have in an application development. If you are developing any web application in any programming language, you can’t escape from these operations. CRUD is nothing but an abbreviation to Create, Read, Update and Delete. Moreover, development of CRUD operations is expected from all developers. We will learn ‘Spring Boot Cassandra CRUD Examples’ in this article.

Software/Technologies Used in the Examples

Sometimes some version conflicts with other version. Hence, listing down the combinations that are tested to be working with each other. Below is the tested combination of software that are used to develop these examples. It also makes the implementation flawless.

1) Apache Cassandra 3.11.12 (download Cassandra)
2) Python 2.7 (download Python)
3) JDK 1.8 or later
4) Lombok 1.18.22
5) Spring Data Cassandra 3.3.1
6) Spring Boot 2.6.3
7) Spring Framework 5.3.15
8) Maven 3.8.1
9) IDE – STS 4.7.1.RELEASE (Starters: Spring Data for Apache Cassandra, Lombok)

How to make Cassandra DB ready to connect with Spring Boot?

Step#1: Download & Install Apache Cassandra

If you don’t have Cassandra installed on your local system, kindly follow a separate article on ‘How to Install Cassandra in the local System?‘.

Step#2: Start the Cassandra DB

Open a command prompt(‘cmd’) window and navigate to the bin directory of your Cassandra installation(e.g. ‘C:\apache-cassandra-3.11.12\bin’). Now type cassandra to run it. Now observe the message ‘Starting Cassandra Server’ at the start or ‘startup complete’ at the bottom lines.

C:\apache-cassandra-3.11.12\bin>cassandra

How to create Keyspace & table in Cassandra DB?

In order to work with Apache Cassandra, we have to create a keyspace and a table by following below steps.

Step#1: Create a keyspace

Open a new command prompt(cmd) and type cqlsh as below:

C:\Users\username>cqlsh

Create a keyspace by providing below command. The name of keyspace is ‘invoicedata’ in my case.

CREATE KEYSPACE invoicedata WITH REPLICATION = {'class' : 'SimpleStrategy','replication_factor' : 1};

Your keyspace is created. Now provide below command to use created keyspace.

cqlsh> use invoicedata;

you will see below line which means you are in ‘invoicedata’ keyspace.

cqlsh:invoicedata>

If you want to drop a keyspace due to any reason, provide below command:

cqlsh:invoicedata> drop keyspace mykeyspace;

Step#2: Create a table

In order to create a table named ‘invoice’, provide below command:

cqlsh:invoicedata> create table invoice(id int primary key, name text, number text, amount double);

If you want to drop a table due to any reason, provide below command:

cqlsh:invoicedata> drop table invoicedata.mytable;

In order to check the structure of created table, provide the below command:

cqlsh:invoicedata> select * from invoice;

A keyspace is the outermost container for data in Cassandra. It has a name and attributes that define its behavior. While the most common scenario is to have one keyspace per application, you could choose to segment your data into multiple keyspaces as well. Tables are located in keyspaces. A keyspace defines options that apply to all the keyspace’s tables.

By default, keyspace and table names are case-insensitive (myTable is equivalent to mytable) but case sensitivity can be forced by using double-quotes (“myTable” is different from mytable). Further, a table is always part of a keyspace and a table name can be provided fully-qualified by the keyspace it is part of. If it is not fully-qualified, the table is assumed to be in the current keyspace in the context.

What is CassandraRepository<T, ID> ?

CassandraRepository<T, ID> is an important interface which will help us to write CRUD operations easily by providing some predefined methods in it. For example, in our case we will extend our custom Repository interface from this interface. However, It is just like JpaRepositry<T, ID> that we use to write CRUD operations in case of SQL databases. Moreover, it extends CrudRepository<T, ID> interface which further extends and Repository<T, ID> interface. While using Cassandra DB with Spring Boot, we often use this interface to develop CRUD operations easily. Below is the dependency that we add in pom.xml in order to get predefined methods provided by this interface.

<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-cassandra</artifactId>
</dependency>

Spring Boot Cassandra CRUD Examples

In this section, we will discuss about the Spring Boot Cassandra CRUD examples in detail. We will start with creating a Spring Boot Starter Project. In the following steps we will set up the configurations in order to connect Cassandra DB with our Spring Boot Application. Next, we will use CassandraRepository to create CRUD operations. Finally, we will test the outputs of created operations from the console and Cassandra DB as well. Let’s start implementing our example step by step.

Use case Details

We will consider an Invoice as a model to create a table in Cassandra DB and implement CRUD Operations accordingly.

Minimum Software Required

♠ Apache Cassandra 3.11.12
♦ JDK 8 and Above
♠ Python 2.7

Prerequisite

1) In this article, we are using Cassandra localhost installation. So, in order to implement the Spring Boot Cassandra CRUD Examples, We need to have a Cassandra DB installed in our local system. For a complete installation of Cassandra DB and its dependencies, we have a separate article on ‘How to Install Cassandra in the local System?‘. Kindly refer this article to get Apache Cassandra installed on your local system easily.

2) Before running your Spring Boot Application, start the Cassandra DB and create a keyspace & a table as described in the above sections.

Step#1 : Create a Spring Boot Project using STS(Spring Tool Suite)

Here, we will use STS(Spring Tool Suite) to create our Spring Boot Project. If you are new to Spring Boot, visit a link on How to create a sample project in spring boot using STS?. While creating a project in STS, add starters ‘Spring Data for Apache Cassandra’, and ‘Lombok’ in order to get the features of Cassandra DB. Furthermore, if you are new to ‘Lombok’, kindly visit ‘How to configure Lombok‘ and to know all about it in detail. Please note that Lombok dependency is optional. If you want to create getters/setters & constructors with the help of IDE, you can ignore to add this dependency.

Step#2 : Update application.properties

Update application.properties as below.

spring.data.cassandra.keyspace-name=invoicedata
spring.data.cassandra.contact-points=localhost
spring.data.cassandra.port=9042
spring.data.cassandra.schema-action=NONE

Here, schema-action=NONE indicates that we do not want our database to be created or recreated on startup. The rest of the attributes are used by Spring Data to connect to the correct Cassandra cluster.

Step#4 : Create a Configuration class for Cassandra DB

We need to create a @Configuration class for setting up Spring beans for Cluster and Session instances. Spring Data Cassandra provides an AbstractCassandraConfiguration base class to reduce the configuration code needed.

package com.dev.springboot.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;

@Configuration
public class CassandraConfiguration extends AbstractCassandraConfiguration {

       @Value("${spring.data.cassandra.keyspace-name}")
       private String keySpace;

       @Value("${spring.data.cassandra.contact-points}")
       private String contactPoints;

       @Value("${spring.data.cassandra.port}")
       private int port;

       /*
        * Provide a keyspace name to the configuration.
        */
       @Override
       public String getKeyspaceName() {
           return keySpace;
       }

       @Override
       public String getContactPoints() {
           return contactPoints;
       }

       @Override
       public int getPort() {
           return port;
       }
}

Step#5 : Create a model class to map it as a table in Cassandra DB

Here we will use Invoice as a model class to illustrate the examples. Hence create an Invoice.java class as below.

package com.dev.springboot.entity;

import org.springframework.data.cassandra.core.mapping.PrimaryKey;
import org.springframework.data.cassandra.core.mapping.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Table //represents that it will map to ‘invoice’ table in Cassandra DB.
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Invoice {

     @PrimaryKey
     private Integer id;
     private String name;
     private String number;
     private Double amount;
}

Note: We have two noticeable points here. Unlike @Entity at the model class in JPA, we have used @Table in Cassandra. Moreover, unlike @id in JPA, we have used @PrimaryKey in Cassandra.

Step#6 : Create a Repository interface

In order to support DB operations, we will create one Repository interface. As per convention, we name it InvoiceRepository.java which will extend CassandraRepository<Invoice, Integer> as below.

package com.dev.springboot.repository;
import java.util.List;
import org.springframework.data.cassandra.repository.AllowFiltering;
import org.springframework.data.cassandra.repository.CassandraRepository;
import com.dev.springboot.entity.Invoice;

public interface InvoiceRepository extends CassandraRepository<Invoice, Integer> {

//Like other Database Repositories, some commonly used methods are already provided by CassandraRepository.
//Hence, we don't need to write those here. We can write custom methods. 
//For example, below method is a custom method. 
@AllowFiltering
List<Invoice> findByName(String name);
}

@AllowFiltering: When your query needs filtering, Cassandra suggests you to add @AllowFiltering to your method. Moreover, if you don’t want to add @AllowFiltering, you have other options such as change your data model, add an index, use another table etc. You have to make the right choice for your specific use case.

Note for developing CRUD Operations: We will create a Runner class to write and test for each operation in further steps. Moreover, we will name the Runner class prefixed with the operation name to make it understand easier.

save(), saveAll(), insert(): Inserting records Example using Spring Boot & Cassandra

There are four methods provided by CassandraRepository to insert records in DB. These are save(), saveAll(), and 2 flavors of insert() to insert single record & collection of records respectively. For example, Below is the code to understand the data insertion.

package com.dev.springboot.runner;

import java.util.List;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import com.dev.springboot.entity.Invoice;
import com.dev.springboot.repository.InvoiceRepository;

@Component
public class SaveOrInsertOperationRunner implements CommandLineRunner {

      @Autowired
      InvoiceRepository repo;

      @Override
      public void run(String... args) throws Exception {

          //saving one record into Cassandra DB using save() method
          Invoice inv = new Invoice(1,"Inv1","POS34523",295.74);
          repo.save(inv);

          //saving multiple records into Cassandra DB using saveAll() method
          repo.saveAll(List.of(
                   new Invoice(2,"Inv2","POS34522",292.00), 
                   new Invoice(3,"Inv3","QOS34523",293.75),
                   new Invoice(4,"Inv4","ROS34524",294.34),
                   new Invoice(5,"Inv5","SOS34525",295.95),
                   new Invoice(6,"Inv6","TOS34526",296.54),
                   new Invoice(8,"Inv4","WQS34528",247.45)
                   )
         );

         //saving one record into Cassandra DB using insert() method
         repo.insert(new Invoice(7,"Inv7","VOS34527",297.65));
      }
}

In order to know the difference between these methods, kindly visit official Spring Data document.

Output 

On querying Cassandra DB, below is the output.

Cassandra-Savedata

save(), insert(): Updating records using Spring Boot & Cassandra

Like other popular database repositories, there is no specific methods to update records in Cassandra DB as well. save() & insert() itself facilitates us to update the records. For example, below code demonstrates the concept.

package com.dev.springboot.runner;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import com.dev.springboot.entity.Invoice;
import com.dev.springboot.repository.InvoiceRepository;

@Component
public class UpdateOperationRunner implements CommandLineRunner {

      @Autowired
      InvoiceRepository repo;

      @Override
      public void run(String... args) throws Exception {

          //Update Invoice Number from 'Inv1' to 'Inv01' using save() where id=1
          repo.save(new Invoice(1,"Inv01","POS34523",295.74));

          //Update Invoice Amount from '294.34' to '395.24' using insert() where id=4
          repo.insert(new Invoice(4,"Inv4","ROS34524",395.24));
      }
}

♥ Note: Because Cassandra uses an append model, there is no fundamental difference between the insert and update operations.  If you insert a row that has the same primary key as an existing row, the row is replaced. If you update a row and the primary key does not exist, Cassandra creates it.

Output

On querying Cassandra DB, below is the output.

Cassandra-Updatedata

findAll(), findById(), findByProperty(): Retrieving records using Spring Boot & Cassandra

Like other popular database repositories, there are methods to retrieve records in Cassandra DB as well. findAll() & findById() are the most popular methods to retrieve the records. Apart from that, we will create an example of findByName() where Name is  property of our model class. For example, below code demonstrates the concept.

package com.dev.springboot.runner;

import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import com.dev.springboot.entity.Invoice;
import com.dev.springboot.repository.InvoiceRepository;

@Component
public class FindOperationRunner implements CommandLineRunner {

      @Autowired
      InvoiceRepository repo;

      @Override
      public void run(String... args) throws Exception {

      //Retrive all records using findAll() method
      List<Invoice> invoices = repo.findAll();
      invoices.forEach(System.out::println);

      //Retrive record by Id using findById() method
      Optional<Invoice> opt= repo.findById(5);
      if(opt.isPresent()) {
           System.out.println(opt.get().getName());;
      }

      //Retrive records by invoice name using findByName() method
      List<Invoice> invoicesByName = repo.findByName("Inv4");
      invoicesByName.forEach(System.out::println);

      }
}

Output

On running the application, you can see below output on the console.

Invoice(id=5, name=Inv5, number=SOS34525, amount=295.95)
Invoice(id=1, name=Inv01, number=POS34523, amount=295.74)
Invoice(id=8, name=Inv4, number=WQS34528, amount=247.45)
Invoice(id=2, name=Inv2, number=POS34522, amount=292.0)
Invoice(id=4, name=Inv4, number=ROS34524, amount=395.24)
Invoice(id=7, name=Inv7, number=VOS34527, amount=297.65)
Invoice(id=6, name=Inv6, number=TOS34526, amount=296.54)
Invoice(id=3, name=Inv3, number=QOS34523, amount=293.75)

Inv5

Invoice(id=8, name=Inv4, number=WQS34528, amount=247.45)
Invoice(id=4, name=Inv4, number=ROS34524, amount=395.24)

deleteAll(), deleteById(): Removing records using Spring Boot & Cassandra

deleteAll() & deleteById() are the most popular methods to retrieve the records. As the name suggests, deleteAll() will delete all the records from the Cassandra DB, whereas deleteById() will delete a single record against that id. For example, below code demonstrates the concept.

package com.dev.springboot.runner;

import java.util.List;
Import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import com.dev.springboot.entity.Invoice;
import com.dev.springboot.repository.InvoiceRepository;

@Component
public class DeleteOperationRunner implements CommandLineRunner {

     @Autowired      
     InvoiceRepository repo;

     @Override 
     public void run(String... args) throws Exception {

         //Remove a record where id=2 using deleteById() method
         repo.deleteById(2);

         //Retrive all records using findAll() method
         List<Invoice> invoices = repo.findAll();
         invoices.forEach(System.out::println);
      }
}

Output

On running the application, the record with id=2 will get removed from the DB. Also, you can see below output on the console.

Invoice(id=5, name=Inv5, number=SOS34525, amount=295.95)
Invoice(id=1, name=Inv01, number=POS34523, amount=295.74)
Invoice(id=8, name=Inv4, number=WQS34528, amount=247.45)
Invoice(id=4, name=Inv4, number=ROS34524, amount=395.24)
Invoice(id=7, name=Inv7, number=VOS34527, amount=297.65)
Invoice(id=6, name=Inv6, number=TOS34526, amount=296.54)
Invoice(id=3, name=Inv3, number=QOS34523, amount=293.75)

What is @AllowFiltering annotation in Cassandra?

The annotation @AllowFiltering is a very new term specially for those who are going to use Cassandra for the first time. Hence, it becomes important to know about it. If you don’t apply it to your custom method in Repository interface, you may face below exception.

“com.datastax.oss.driver.api.core.servererrors.InvalidQueryException: Cannot execute this query as it might involve data filtering and thus may have unpredictable performance. If you want to execute this query despite the performance unpredictability, use ALLOW FILTERING”

What happens when you write ‘Where’ clause in your Query?

When we impose a filter condition on our query (select * from invoice where name=’Inv1′), we will receive the above exception in response. Cassandra knows that it might not be able to execute the query in an efficient way. The only way Cassandra can execute this query is by retrieving all the rows from the table invoice and then by filtering out the ones which do not have the requested value for the name column.

For example, Suppose your table contains one thousand rows and 90% of them have the requested value for the name column, the query will still be relatively efficient and you should use ALLOW FILTERING.

On the other hand, suppose your table contains one thousand rows and only 4 rows contain the requested value for the name column, your query is extremely inefficient. Cassandra will load 996 rows for nothing. In this case, instead of using this query it is probably better to add an index on the name column.

Unfortunately, Cassandra has no way to differentiate between the 2 cases above as they are depending on the data distribution of the table. Therefore, Cassandra warns you and rely on you to make a good choice.

When Cassandra rejects your query just because it needs filtering, you should add @AllowFiltering to your method. You should think about your data, your model and what you are trying to do. You always have multiple options such as change your data model, add an index, use another table or use ALLOW FILTERING. You have to make the right choice for your specific use case.

close

Leave a Reply

Top