Trong bài viết này, chúng ta sẽ xây dựng một ứng dụng Spring Boot mẫu và tích hợp Redis cache vào đó. Redis là một kho lưu trữ cấu trúc dữ liệu trong bộ nhớ (in-memory) mã nguồn mở, thường được dùng làm database, cache và message broker (trình trung gian truyền thông điệp). Trong khuôn khổ bài này, ta sẽ chỉ tập trung vào việc tích hợp cache.

Cài đặt dự án tích hợp Redis cache vào Spring Boot
Chúng ta sẽ dùng công cụ Spring Initializr để khởi tạo nhanh dự án. Ta sẽ cần 3 dependency như hình bên dưới:

Tải dự án về và giải nén. Ta dùng dependency của H2 Database vì chúng ta sẽ sử dụng một database nhúng, vốn sẽ mất toàn bộ dữ liệu mỗi khi ứng dụng dừng lại.
Các dependency của Maven
Mặc dù chúng ta đã hoàn tất việc thiết lập bằng công cụ trên, nếu bạn muốn thiết lập thủ công, chúng ta sẽ sử dụng hệ thống build Maven cho dự án này. Đây là các dependency cần dùng:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- for JPA support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- for embedded database support -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Đảm bảo rằng bạn sử dụng phiên bản ổn định (stable version) của Spring Boot từ Maven Central.
Định nghĩa Model
Để lưu một đối tượng vào Redis, chúng ta cần định nghĩa một đối tượng model Person với các trường cơ bản:
package com.journaldev.rediscachedemo;
import javax.persistence.*;
import java.io.Serializable;
@Entity
public class User implements Serializable {
private static final long serialVersionUID = 7156526077883281623L;
@Id
@SequenceGenerator(name = "SEQ_GEN", sequenceName = "SEQ_USER", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_GEN")
private Long id;
private String name;
private long followers;
public User() {
}
public User(String name, long followers) {
this.name = name;
this.followers = followers;
}
//standard getters and setters
@Override
public String toString() {
return String.format("User{id=%d, name='%s', followers=%d}", id, name, followers);
}
}
Đây là một POJO tiêu chuẩn với các phương thức getter và setter (các phương thức lấy và thiết lập thuộc tính).
Cấu hình Redis cache
Khi Spring Boot và các dependency cần thiết đã được thêm vào Maven, ta có thể cấu hình một thực thể Redis cục bộ chỉ với ba dòng sau trong file application.properties:
# Redis Config
spring.cache.type=redis
spring.redis.host=localhost
spring.redis.port=6379
Ngoài ra, hãy thêm annotation @EnableCaching vào class chính của Spring Boot:
package com.journaldev.rediscachedemo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class Application implements CommandLineRunner {
private final Logger LOG = LoggerFactory.getLogger(getClass());
private final UserRepository userRepository;
@Autowired
public Application(UserRepository userRepository) {
this.userRepository = userRepository;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... strings) {
//Populating embedded database here
LOG.info("Saving users. Current user count is {}.", userRepository.count());
User shubham = new User("Shubham", 2000);
User pankaj = new User("Pankaj", 29000);
User lewis = new User("Lewis", 550);
userRepository.save(shubham);
userRepository.save(pankaj);
userRepository.save(lewis);
LOG.info("Done saving users. Data: {}.", userRepository.findAll());
}
}
Ta thêm một CommandLineRunner để khởi tạo một vài dữ liệu mẫu trong dữ liệu H2 nhúng.
Định nghĩa repository
Trước khi trình bày cách Redis hoạt động, chúng ta sẽ định nghĩa một repository (kho lưu trữ) cho các chức năng liên quan đến JPA:
package com.journaldev.rediscachedemo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository { }
Hiện tại nó không có phương thức nào vì chúng ta chưa cần đến.
Định nghĩa controller
Controller là nơi mà Redis cache được gọi để thực thi. Trên thực tế, đây là nơi tốt nhất để làm điều đó vì khi cache được liên kết trực tiếp với controller, request thậm chí sẽ không cần đi vào đến tầng service để chờ kết quả được cache.
Dưới đây là code cơ bản của controller:
package com.journaldev.rediscachedemo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;
@RestController
public class UserController {
private final Logger LOG = LoggerFactory.getLogger(getClass());
private final UserRepository userRepository;
@Autowired
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
...
}
Để đưa một đối tượng vào cache, ta sử dụng annotation @Cacheable:
@Cacheable(value = "users", key = "#userId", unless = "#result.followers < 12000")
@RequestMapping(value = "/{userId}", method = RequestMethod.GET)
public User getUser(@PathVariable String userId) {
LOG.info("Getting user with ID {}.", userId);
return userRepository.findOne(Long.valueOf(userId));
}
Trong mapping (ánh xạ) trên, phương thức getUser() sẽ đưa một Person vào cache có tên là ‘users’, định danh Person đó bằng key ‘userId’, và sẽ chỉ lưu trữ user có số follower lớn hơn 12000. Điều này đảm bảo rằng cache chỉ chứa những người dùng nổi tiếng và thường xuyên được truy vấn. Chúng ta cũng đã cố ý thêm một câu lệnh log trong lệnh gọi API.
Hãy thử thực hiện một vài lệnh gọi API từ Postman sau đây:
localhost:8090/1
localhost:8090/1
localhost:8090/2
localhost:8090/2
Nếu để ý phần log, bạn sẽ thấy:
... : Getting user with ID 1.
... : Getting user with ID 1.
... : Getting user with ID 2.
Ta đã thực hiện bốn lệnh gọi API nhưng chỉ có ba câu lệnh log được ghi lại. Đó là vì User có ID 2 với 29000 follower đã được cache lại. Điều này có nghĩa là khi một lệnh gọi API được thực hiện cho user này, dữ liệu đã được trả về từ cache mà không cần thực hiện lệnh gọi nào đến cơ sở dữ liệu nào cả.
Cập nhật cache
Các giá trị trong cache cũng nên được cập nhật bất cứ khi nào giá trị của đối tượng thực tế thay đổi. Điều này có thể được thực hiện bằng cách sử dụng annotation @CachePut:
@CachePut(value = "users", key = "#user.id")
@PutMapping("/update")
public User updatePersonByID(@RequestBody User user) {
userRepository.save(user);
return user;
}
Với annotation này, một Person lại được định danh bằng ID của mình và được cập nhật với kết quả mới.
Xóa cache
Nếu một số dữ liệu bị xóa khỏi cơ sở dữ liệu, việc giữ nó trong cache không còn ý nghĩa gì nữa. Chúng ta có thể xóa dữ liệu cache bằng annotation @CacheEvict:
@CacheEvict(value = "users", allEntries=true)
@DeleteMapping("/{id}")
public void deleteUserByID(@PathVariable Long id) {
LOG.info("deleting person with id {}", id);
userRepository.delete(id);
}
Trong mapping cuối cùng, chúng ta chỉ xóa các mục trong cache và không làm gì khác.
Chạy ứng dụng tích hợp Spring Boot và Redis
Chúng ta có thể chạy ứng dụng này một cách đơn giản bằng một lệnh duy nhất:
mvn spring-boot:run
Giới hạn của Redis cache
Mặc dù Redis rất nhanh, nó vẫn có giới hạn. Redis không giới hạn dung lượng lưu trữ trên hệ thống 64-bit, nhưng chỉ có thể lưu trữ 3GB dữ liệu trên hệ thống 32-bit.
Việc có nhiều bộ nhớ hơn có thể dẫn đến tỉ lệ cache hit (trường hợp dữ liệu được lấy từ cache) cao hơn, nhưng tỉ lệ này sẽ giảm dần khi Redis chiếm dụng quá nhiều bộ nhớ. Khi kích thước cache đạt đến giới hạn bộ nhớ, dữ liệu cũ sẽ bị xóa để nhường chỗ cho dữ liệu mới.
Tổng kết
Trong bài này, chúng ta đã xem xét khả năng mà Redis cache mang lại trong việc tương tác dữ liệu nhanh chóng, và cách chúng ta có thể tích hợp nó với Spring Boot với cấu hình tối thiểu nhưng đầy đủ tính năng. Việc khai thác cache có điều kiện giúp kiểm soát tốt hơn mức độ lưu trữ và làm mới dữ liệu trong cache. Bạn có thể áp dụng những kiến thức này khi phát triển các chức năng ghi chú, lưu tạm dữ liệu hoặc giảm tải truy cập dữ liệu thường xuyên.