Software/Technologies Used in the Examples
Sometimes a specific version conflicts with another version. In order to avoid such conflicts, listing down all the combinations that are tested to be working with each other. Below is the tested combination of software that are used to develop Spring Boot Cassandra CRUD 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)
What is Cassandra?
Cassandraย is a free and open-source, distributed, NoSQL database management system intended to deal with huge amounts of data over many commodity servers, delivering high availability with no single point of failure. Since it is provided by Apache Software Foundation, we generally call it Apache Cassandra.
How to make Cassandra DB ready to connect with Spring Boot?
Before implementing โSpring Boot Cassandra CRUD Examplesโ, letโs have a proper setup of Cassandra DB in the system.
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;
What is Keyspace in Cassandra?
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 Spring Boot Cassandra CRUD Examples 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 Spring Boot Cassandra CRUD Examples accordingly.
Minimum Software Required
โ Apache Cassandra 3.11.12
โฆ JDK 8 and Above
โ Python 2.7
Prerequisite
Below are the prerequisites to develop Spring Boot Cassandra CRUD Examples.
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)
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 Spring Boot Cassandra CRUD 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 of Spring Boot Cassandra CRUD Examples, 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 in Spring Boot Cassandra CRUD Examples.
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.
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.
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 delete the records for Spring Boot Cassandra CRUD Examples. 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)
FAQ
What is @AllowFiltering annotation in Cassandra & When to use it?
If you are developing Spring Boot Cassandra CRUD Examples, it becomes important to know about @AllowFiltering.
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โ
When your query needs filtering, Cassandra suggests you to add @AllowFiltering to your method. Letโs understand it from the example below:
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, imagine that 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.
ย