Triển khai API CRUD với Cassandra và Spring Boot giúp cho việc quản lý dữ liệu trở nên đơn giản và hiệu quả hơn, đồng thời cung cấp khả năng lưu trữ và truy xuất dữ liệu lớn.

Bài hôm nay chúng ta sẽ triển khai một dự án Spring boot nho nhỏ, để demo cách sử dụng Cassandra với Spring.

Cấu trúc dự án cassandra spring boot

Các bạn làm lần lượt theo cấu trúc bên dưới

cassandra-spring-struct-project
package com.cafeincode.cassandra.entity;

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

import java.util.UUID;

@Table("user")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @PrimaryKey
    private UUID id;

    private String name;

    private Integer age;
}
package com.cafeincode.cassandra.mapper;

import com.cafeincode.cassandra.entity.User;
import org.mapstruct.*;

@Mapper(componentModel = "spring")
public interface UserMapper {

    @Mapping(source = "id", target = "id", ignore = true)
    User from(User user);

    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    @Mapping(source = "id", target = "id", ignore = true)
    void updateFields(User input, @MappingTarget User target);
}
package com.cafeincode.cassandra.repository;

import com.cafeincode.cassandra.entity.User;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.stereotype.Repository;

import java.util.UUID;

@Repository
public interface UserRepository extends CassandraRepository<User, UUID> {

}
package com.cafeincode.cassandra.service;

import com.cafeincode.cassandra.entity.User;
import com.cafeincode.cassandra.mapper.UserMapper;
import com.cafeincode.cassandra.repository.UserRepository;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.UUID;

@Service
@AllArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final UserMapper userMapper;

    @Transactional(rollbackFor = Exception.class)
    public User createUser(User request) {
        var jpaUser = userMapper.from(request);
        jpaUser.setId(Uuids.timeBased());
        return userRepository.save(jpaUser);
    }

    @Transactional(rollbackFor = Exception.class)
    public User updateUser(UUID id, User request) {
        var jpaUser = userRepository.findById(id);
        if (jpaUser.isEmpty()) {
            throw new RuntimeException("User find not found");
        }
        var jpaUpdated = jpaUser.get();
        userMapper.updateFields(request, jpaUpdated);
        return userRepository.save(jpaUpdated);
    }

    public User findById(UUID id) {
        return userRepository.findById(id).orElseThrow(RuntimeException::new);
    }

    @Transactional(rollbackFor = Exception.class)
    public void deleteById(UUID id) {
        userRepository.deleteById(id);
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
}
spring.data.cassandra.local-datacenter=datacenter1
spring.data.cassandra.keyspace-name=cafeincode_keyspace
spring.data.cassandra.contact-points=localhost
spring.data.cassandra.port=9042
spring.data.cassandra.request.consistency=quorum

server.port=8888
version: '3.9'

services:
  node1:
    image: cassandra:4.0
    container_name: cassandra-node-cafeincode
    restart: always
    environment:
      - CASSANDRA_CLUSTER_NAME=cassandra-cafeincode-cluster
      - CASSANDRA_DC=datacenter1
      - CASSANDRA_RACK=rack1
      - CASSANDRA_SEEDS=node1
    volumes:
      - ./cassandra-data/node1:/var/lib/cassandra/data
      - ./cassandra-logs/node1:/var/log/cassandra
    ports:
      - "9042:9042"
      - "7000:7000"
      - "7001:7001"
      - "7199:7199"
      - "9160:9160"
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.cafeincode</groupId>
    <artifactId>cassandra-spring</artifactId>
    <version>0.0.1</version>
    <name>cassandra-spring-example</name>
    <description>Restful API using Cassandra and Spring boot</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-cassandra</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.5.3.Final</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.5.3.Final</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
package com.cafeincode.cassandra.controller;

import com.cafeincode.cassandra.entity.User;
import com.cafeincode.cassandra.service.UserService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/api/users")
@AllArgsConstructor
public class UserController {

    private final UserService userService;

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User request) {
        var createdUser = userService.createUser(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }

    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable UUID id, @RequestBody User request) {
        var updatedUser = userService.updateUser(id, request);
        return ResponseEntity.ok(updatedUser);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable UUID id) {
        userService.deleteById(id);
        return ResponseEntity.noContent().build();
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable UUID id) {
        var user = userService.findById(id);
        return ResponseEntity.ok(user);
    }

    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        var users = userService.getAllUsers();
        return ResponseEntity.ok(users);
    }
}

Phần Controller mình viết một số api đơn giản, create/update/delete/get detail/get all, phần logic chi tiết các bạn có thể xem ở file UserService, ngoài ra cuối bài cũng có link source code để các bạn tham khảo thêm.

Tạo key space trong cassandra

Trước tiên phải start Docker lên trước, sau đấy dùng lệnh docker-compose up -d để triển khai container cassandra

docker compose for cassandra spring boot project
docker compose up -d

Sau đó vào thẳng container trong Docker Hub để tương tác với cassandra, dùng command cqlsh để kết nối vào cassandra cluster

using cqlsh in cassandra spring project
cqlsh

Cũng giống như một vài loại cơ sở dữ liệu mà các bạn đã quen thuộc, thì chúng ta phải tạo schema trước rồi mới tạo table sau, đối với cassandra thì key space cũng tương tự như schema vậy.

Mình dùng đoạn lệnh bên dưới để khởi tạo một key space cafeincode_keyspace

create keyspace cafeincode_keyspace with replication={'class':'SimpleStrategy','replication_factor': '1'};
create key space in cassandra spring project
create key space

Sau khi tạo xong key space, chúng ta sẽ tạo một bảng user, các bạn nhớ phải sử dụng đoạn lệnh use + schema thì mới tạo được bảng.

CREATE TABLE user (
    id UUID,
    name text,
    age int,
    PRIMARY KEY (id)
);
create table in cassandra spring project
create table user

dùng lệnh describe user để xem lại thông tin bảng chúng ta đã tạo bên trên

describe table in cassandra spring project
describe user

Check lại data trong bảng user chút nào

query table in cassandra spring project
select * from user;

Kiểm thử

Giờ chúng ta sẽ start dự án lên và gọi thử API từ postman để xem dữ liệu đã được lưu trữ trong cassandra hay chưa

start project cassandra spring project

Giờ chúng ta sẽ gọi 3 lần để tạo 3 bản ghi user.

create user 1
create user 2
create user 3
get all user

Xong, giờ sẽ vào query trong cassandra xem dữ liệu như thế nào

select * from user

Vậy là chúng ta đã demo xong việc viết api restful và sử dụng cassandra để lưu trữ dữ liệu.

Các bạn có thể xem triển khai chi tiết tại link sau: cafeincode.com/cassandra

Xem thêm một số bài viết nổi bật: