In this tutorial, we’re gonna build a Spring Boot example that use Spring Data Cassandra to make CRUD operations with Cassandra database and Spring Web MVC for Rest APIs. You’ll know:
- How to configure Spring Data to work with Cassandra Database
- How to define Cassandra Data Models and Cassandra Repository interfaces
- Way to create Spring Rest Controller to process HTTP requests
- Way to use Spring Data Cassandra to interact with Cassandra Database
Exception Handling:
– Spring Boot @ControllerAdvice & @ExceptionHandler example
– @RestControllerAdvice example in Spring Boot
– Document Rest API: Spring Boot Swagger 3 example
– Caching: Spring Boot Redis Cache example
– Validation: Spring Boot Validate Request Body
Contents
Overview of Spring Boot Cassandra CRUD example
We will build a Spring Boot Cassandra Rest CRUD API for a Tutorial application in that:
- Each Tutorial has id, title, description, published status.
- Apis help to create, retrieve, update, delete Tutorials.
- Apis also support custom finder methods such as find by published status or by title.
These are APIs that we need to provide:
Methods | Urls | Actions |
---|---|---|
POST | /api/tutorials | create new Tutorial |
GET | /api/tutorials | retrieve all Tutorials |
GET | /api/tutorials/:id | retrieve a Tutorial by :id |
PUT | /api/tutorials/:id | update a Tutorial by :id |
DELETE | /api/tutorials/:id | delete a Tutorial by :id |
DELETE | /api/tutorials | delete all Tutorials |
GET | /api/tutorials/published | find all published Tutorials |
GET | /api/tutorials?title=[keyword] | find all Tutorials which title contains keyword |
– We make CRUD operations & finder methods using Spring Data Cassandra.
– Rest Controller will be created with the help of Spring Web MVC.
Technology
- Java 8
- Spring Boot 2.2.4 (with Spring Web MVC, Spring Data Cassandra)
- Cassandra 3.9.0
- cqlsh 5.0.1
- Maven 3.6.1
Project Structure
Tutorial
data model class corresponds to entity and table tutorials.TutorialRepository
is an interface that extendsCassandraRepository
for CRUD methods and custom finder methods. It will be autowired inTutorialController
.TutorialController
is a RestController which has request mapping methods for RESTful requests such as: getAllTutorials, createTutorial, updateTutorial, deleteTutorial, findByPublished…- Configuration for Spring Data Cassandra is in application.properties.
- pom.xml contains dependencies for Spring Boot Web MVC and Spring Data Cassandra.
Let’s implement this application right now.
Create & Setup Spring Boot Cassandra project
Use Spring web tool or your development tool (Spring Tool Suite, Eclipse, Intellij) to create a Spring Boot project.
Then open pom.xml and add these dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
</dependency>
Configure Spring Data Cassandra
Under src/main/resources folder, open application.properties and add following lines.
spring.data.cassandra.keyspace-name=bezkoder
spring.data.cassandra.contact-points=127.0.0.1
spring.data.cassandra.port=9042
Define Data Model
Our Data model is Tutorial with four fields: id, title, description, published.
In model package, we define Tutorial
class.
model/Tutorial.java
package com.bezkoder.spring.data.cassandra.model;
import java.util.UUID;
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
import org.springframework.data.cassandra.core.mapping.Table;
@Table
public class Tutorial {
@PrimaryKey
private UUID id;
private String title;
private String description;
private boolean published;
public Tutorial() {
}
public Tutorial(UUID id, String title, String description, boolean published) {
this.id = id;
this.title = title;
this.description = description;
this.published = published;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isPublished() {
return published;
}
public void setPublished(boolean isPublished) {
this.published = isPublished;
}
@Override
public String toString() {
return "Tutorial [id=" + id + ", title=" + title + ", desc=" + description + ", published=" + published + "]";
}
}
– @Table
identifies this model to be persisted to Cassandra as ‘tutorial’ table.
– @PrimaryKey
specifies the primary key field of this entity. This field corresponds to the PRIMARY KEY of the ‘tutorial’ table.
Create Repository Interface
Let’s create a repository to interact with Tutorials from the database.
In repository package, create TutorialRepository
interface that extends CassandraRepository
.
repository/TutorialRepository.java
package com.bezkoder.spring.data.cassandra.repository;
import java.util.List;
import java.util.UUID;
import org.springframework.data.cassandra.repository.AllowFiltering;
import org.springframework.data.cassandra.repository.CassandraRepository;
import com.bezkoder.spring.data.cassandra.model.Tutorial;
public interface TutorialRepository extends CassandraRepository<Tutorial, UUID> {
@AllowFiltering
List<Tutorial> findByPublished(boolean published);
List<Tutorial> findByTitleContaining(String title);
}
Now we can use CassandraRepository’s methods: save()
, findOne()
, findById()
, findAll()
, count()
, delete()
, deleteById()
… without implementing these methods.
We also define custom finder methods:
– findByTitleContaining()
: returns all Tutorials which title contains input title
.
– findByPublished()
: returns all Tutorials with published
having value as input published
.
@AllowFiltering
annotation allows server-side filtering for findByPublished()
method.
Why do we use it?
The method is equivalent to this query:
SELECT * FROM tutorial WHERE published = [true/false];
If we don’t annotate the method with @AllowFiltering
, we will get the error:
Bad Request: 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.
For findByTitleContaining()
, I will show you how to index title column later.
The implementation is plugged in by Spring Data Apache Cassandra automatically.
Create Spring Rest APIs Controller
Finally, we create a controller that provides APIs for creating, retrieving, updating, deleting and finding Tutorials.
controller/TutorialController.java
package com.bezkoder.spring.data.cassandra.controller;
...
import com.bezkoder.spring.data.cassandra.model.Tutorial;
import com.bezkoder.spring.data.cassandra.repository.TutorialRepository;
@CrossOrigin(origins = "http://localhost:8081")
@RestController
@RequestMapping("/api")
public class TutorialController {
@Autowired
TutorialRepository tutorialRepository;
@GetMapping("/tutorials")
public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) {
}
@GetMapping("/tutorials/{id}")
public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") String id) {
}
@PostMapping("/tutorials")
public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
}
@PutMapping("/tutorials/{id}")
public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") String id, @RequestBody Tutorial tutorial) {
}
@DeleteMapping("/tutorials/{id}")
public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") String id) {
}
@DeleteMapping("/tutorials")
public ResponseEntity<HttpStatus> deleteAllTutorials() {
}
@GetMapping("/tutorials/published")
public ResponseEntity<List<Tutorial>> findByPublished() {
}
}
– @CrossOrigin
is for configuring allowed origins.
– @RestController
annotation is used to define a controller and to indicate that the return value of the methods should be be bound to the web response body.
– @RequestMapping("/api")
declares that all Apis’ url in the controller will start with /api
.
– We use @Autowired
to inject TutorialRepository
bean to local variable.
Now I will show you how to implement each controller’s CRUD methods.
Create Operation
We use @PostMapping
annotation for handling POST HTTP requests.
A new Tutorial will be created by CassandraRepository.save()
method.
@PostMapping("/tutorials")
public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
try {
Tutorial _tutorial = tutorialRepository.save(new Tutorial(UUIDs.timeBased(), tutorial.getTitle(), tutorial.getDescription(), false));
return new ResponseEntity<>(_tutorial, HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Retrieve Operations
We use @GetMapping
annotation for handling GET HTTP requests, then Repository’s findAll()
, findByTitleContaining(title)
, findByPublished()
method to get the result.
getAllTutorials()
: returns List of Tutorials, if there istitle
parameter, it returns a List in that each Tutorial contains the titlegetTutorialById()
: returns Tutorial by givenid
findByPublished()
: return published Tutorials
@GetMapping("/tutorials")
public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) {
try {
List<Tutorial> tutorials = new ArrayList<Tutorial>();
if (title == null)
tutorialRepository.findAll().forEach(tutorials::add);
else
tutorialRepository.findByTitleContaining(title).forEach(tutorials::add);
if (tutorials.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(tutorials, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping("/tutorials/{id}")
public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") UUID id) {
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
if (tutorialData.isPresent()) {
return new ResponseEntity<>(tutorialData.get(), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@GetMapping("/tutorials/published")
public ResponseEntity<List<Tutorial>> findByPublished() {
try {
List<Tutorial> tutorials = tutorialRepository.findByPublished(true);
if (tutorials.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(tutorials, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Update Operation
@PutMapping
will help us handle PUT HTTP requests.
– updateTutorial()
receives id
and a Tutorial payload.
– from the id
, we get the Tutorial from database using findById()
method.
– then we use the payload and save()
method for updating the Tutorial.
@PutMapping("/tutorials/{id}")
public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") UUID id, @RequestBody Tutorial tutorial) {
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
if (tutorialData.isPresent()) {
Tutorial _tutorial = tutorialData.get();
_tutorial.setTitle(tutorial.getTitle());
_tutorial.setDescription(tutorial.getDescription());
_tutorial.setPublished(tutorial.isPublished());
return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
Delete Operation
We use @DeleteMapping
for DELETE HTTP requests.
There are 2 methods:
deleteTutorial()
: delete a Tutorial document with givenid
deleteAllTutorials()
: remove all documents in tutorials collection
The operations is done with the help of CassandraRepository’s deleteById()
and deleteAll()
method.
@DeleteMapping("/tutorials/{id}")
public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") UUID id) {
try {
tutorialRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@DeleteMapping("/tutorials")
public ResponseEntity<HttpStatus> deleteAllTutorials() {
try {
tutorialRepository.deleteAll();
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Set up Cassandra Database
Open Cassandra CQL Shell:
– Create Cassandra bezkoder keyspace:
create keyspace bezkoder with replication={'class':'SimpleStrategy', 'replication_factor':1};
– Create tutorial table in the keyspace:
use bezkoder;
CREATE TABLE tutorial(
id timeuuid PRIMARY KEY,
title text,
description text,
published boolean
);
Do you remember that we have findByTitleContaining()
method?
The method executes SELECT * FROM tutorial WHERE tutorial LIKE '%title%';
in Cassandra.
So we need the to create a custom index that has options with mode: CONTAINS
along with analyzer_class
to make case_sensitive
effective.
Run the command:
CREATE CUSTOM INDEX idx_title ON bezkoder.tutorial (title)
USING 'org.apache.cassandra.index.sasi.SASIIndex'
WITH OPTIONS = {
'mode': 'CONTAINS',
'analyzer_class': 'org.apache.cassandra.index.sasi.analyzer.NonTokenizingAnalyzer',
'case_sensitive': 'false'};
Run & Test
Run Spring Boot application with command: mvn spring-boot:run
.
Create some Tutorials:
Check Cassandra database, you can see them here:
Retrieve all Tutorials:
Retrieve one Tutorial by Id:
Update some Tutorials:
Check Cassandra database after updating:
Find all published Tutorials:
Find all Tutorials which title contains ‘ring’:
Delete a Tutorial:
Check Cassandra database after deleting the Tutorial:
Delete all Tutorials:
You can also test this Spring Boot App with Client in one of these posts:
- Simple HTTP Client using Axios
- Simple HTTP Client using Fetch API
- Angular 8 CRUD Application example with Web API
- Angular 10 CRUD Application example with Web API
- Angular 11 CRUD Application example with Web API
- Angular 12 CRUD Application example with Web API
- Angular 13 CRUD Application example with Web API
- Angular 14 CRUD Application example with Web API
- Angular 15 CRUD Application example with Web API
- Vue 2 CRUD Application with Vue Router & Axios
- Vue 3 CRUD Application with Axios & Vue Router
- React CRUD example with Axios
- React Redux CRUD example
You may need to handle Exception with:
– Spring Boot @ControllerAdvice & @ExceptionHandler example
– @RestControllerAdvice example in Spring Boot
Conclusion
Today we’ve built a Rest CRUD API using Spring Boot, Spring Data Cassandra and Spring Web MVC to create, retrieve, update, delete documents in Cassandra database.
We also see that CassandraRepository
supports a great way to make CRUD operations and custom finder methods without need of boilerplate code.
Happy learning! See you again.
Source Code
You can find the complete source code for this tutorial on Github.
Documentation: Spring Boot + Swagger 3 example (with OpenAPI 3)
Caching: Spring Boot Redis Cache example
Validation: Spring Boot Validate Request Body
This is the Spring Boot tutorial which is close to my need…
Thank you!
many thanks!
Hi! Thank you for the tutorial, really appreciate it!
One small question: every time I do a “deleteAll”, the running (local, in this case) Cassandra server crashes, thus giving a 500 code and making further operations impossible. What can I do?
What a great tutorial!!! Very well explained. Thank you very much.
But i needed to add spring.data.cassandra.local-datacenter=datacenter1 to application.properties file to start Spring Boot application successfully.
Thank your very much! This is the ONLY tutorial in the web that’s updated and doesn’t contain useless and confusing boiler plate.
Very appreciated!
Very good tutorial . I am having some issues to get connected to Cassandra when authentication is required. Would you have an idea on how to do that?
Really helpful for application using Cassandra with detailed approach
Like!! I blog frequently and I really thank you for your content. The Spring Cassandra tutorial has truly peaked my interest.
Hi Bezkoder,
I have tried this code on my system. But not able to do post/get.
While doing post –> 404Not Found error is coming.
Can you please help me. I am a newbie in spring-boot and Cassandra.
Thanks
Hi, please show me your full POST url, alse make sure that Spring Boot app run successfully.