spring-boot-cassandra-crud-example-feature-image

Spring Boot Cassandra CRUD example with Spring Data

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


Overview of Spring Boot Cassandra CRUD example

We will build a Spring Boot Cassandra Rest CRUD API for a Tutorial application in that:

  • Each Tutotial 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:

MethodsUrlsActions
POST/api/tutorialscreate new Tutorial
GET/api/tutorialsretrieve all Tutorials
GET/api/tutorials/:idretrieve a Tutorial by :id
PUT/api/tutorials/:idupdate a Tutorial by :id
DELETE/api/tutorials/:iddelete a Tutorial by :id
DELETE/api/tutorialsdelete all Tutorials
GET/api/tutorials/publishedfind 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

spring-boot-cassandra-crud-example-project-structure

  • Tutorial data model class corresponds to entity and table tutorials.
  • TutorialRepository is an interface that extends CassandraRepository for CRUD methods and custom finder methods. It will be autowired in TutorialController.
  • 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 is title parameter, it returns a List in that each Tutorial contains the title
  • getTutorialById(): returns Tutorial by given id
  • 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 given id
  • 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:

spring-boot-cassandra-crud-example-create

Check Cassandra database, you can see them here:

spring-boot-cassandra-crud-example-create-db

Retrieve all Tutorials:

spring-boot-cassandra-crud-example-retrieve-all

Retrieve one Tutorial by Id:

spring-boot-cassandra-crud-example-retrieve-one

Update some Tutorials:

spring-boot-cassandra-crud-example-update

Check Cassandra database after updating:

spring-boot-cassandra-crud-example-update-db

Find all published Tutorials:

spring-boot-cassandra-crud-example-find-published

Find all Tutorials which title contains ‘ring’:

spring-boot-cassandra-crud-example-find-by-title

Delete a Tutorial:

spring-boot-cassandra-crud-example-delete-one

Check Cassandra database after deleting the Tutorial:

spring-boot-cassandra-crud-example-delete-one-db

Delete all Tutorials:

spring-boot-cassandra-crud-example-delete-all

You can also test this Spring Boot App with Client in one of these posts:

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.

Further Reading

6 thoughts to “Spring Boot Cassandra CRUD example with Spring Data”

  1. 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

  2. Like!! I blog frequently and I really thank you for your content. The Spring Cassandra tutorial has truly peaked my interest.

  3. 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?

  4. 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!

Leave a Reply

Your email address will not be published. Required fields are marked *