Code ví dụ Spring Boot Caching (@Cacheable, @CacheEvict, @CachePut, @Caching)

Code ví dụ Spring Boot Caching (@Cacheable, @CacheEvict, @CachePut, @Caching)

Trong bài này mình sẽ làm ví dụ về spring boot cache để lưu dữ liệu trong in memory cache làm tăng tốc độ truy vấn dữ liệu

 

1. Code ví dụ Spring Boot Caching (@Cacheable, @CacheEvict, @CachePut, @Caching)

Cấu trúc project:

Code ví dụ Spring Boot Caching (@Cacheable, @CacheEvict, @CachePut, @Caching)

Thư viện sử dụng:

<?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.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>stackjava.com</groupId>
    <artifactId>spring-boot-caching</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-caching</name>
    <description>Demo caching with spring boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

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

</project>

Thư viện spring-boot-starter-cache để dùng cache

File models:

package stackjava.com.springbootcaching.models;

public class User {
    private int id;
    private String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" + "id=" + id + ", name='" + name + '\'' + '}';
    }
}

File services:

package stackjava.com.springbootcaching.services;

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import stackjava.com.springbootcaching.models.User;

@Service
public class UserService {

    @Cacheable("user")
    public User findUserById(int id) {
        simulateSlowService();
        return new User(id, "Any name");
    }

    // Don't do this at home
    private void simulateSlowService() {
        try {
            long time = 3000L;
            Thread.sleep(time);
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }
    }

    @CacheEvict("user")
    public void clearCacheById(int id) {
    }

    @CacheEvict(value = "user", allEntries = true)
    public void clearCache() {
    }

    @CachePut(value = "user")
    public User reloadAndFindUserById(int id) {
        simulateSlowService();
        return new User(id, "reload Any name");
    }
}
  • hàm findUserById mình sử dụng annotation @Cacheable("user") tức là kết quả của hàm này sẽ được lưu vào bộ nhớ cache với tên là user (cái tên này các bạn thích đặt là gì cũng được). Tức là mỗi lần gọi hàm này nó sẽ kiểm tra với id truyền vào đã có dữ liệu trong cache chưa, nếu có rồi thì nó lấy từ trong cache. Nếu chưa có thì nó mới thực sự chạy vào code bên trong hàm và kết quả trả về sẽ được lưu vào cache với id và đối tượng User tương ứng.
  • hàm simulateSlowService để mô phỏng các thao tác như truy cập database, kiểm tra id…, các thao tác này thường sẽ tốn thời gian. Ở đây mình giả sử các thao tác đó sẽ mất 3s để dễ so sánh.
  • hàm clearCacheById được đánh dấu annotation @CacheEvict("user") tức là mỗi lần bạn gọi hàm này nó sẽ xóa dữ liệu với id tương ứng ở trong cache user
  • hàm clearCache được đánh dấu  @CacheEvict(value = "user", allEntries = true)tức là mỗi lần gọi hàm này nó sẽ xóa toàn bộ dữ liệu ở trong cache.
  • hàm reloadAndFindUserById được đánh dấu là @CachePut(value = "user")tức là dữ liệu trả về sẽ được ghi đè vào cache. Với các method được đánh dấu là @CachePut thì sẽ luôn được thực thi kể cả đã có dữ liệu trong cache hay chưa.

File SpringBootCachingApplication.java

package stackjava.com.springbootcaching;

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;
import stackjava.com.springbootcaching.services.UserService;

@SpringBootApplication
@EnableCaching
public class SpringBootCachingApplication implements CommandLineRunner {
    private static final Logger logger = LoggerFactory.getLogger(SpringBootCachingApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(SpringBootCachingApplication.class, args);
    }

    @Autowired
    private UserService userService;

    @Override
    public void run(String... args) {
        logger.info("------------------ demo @Cacheable --------------------");
        logger.info("find user with id = 1: {}", userService.findUserById(1));
        logger.info("find user with id = 1: {}", userService.findUserById(1));
        logger.info("find user with id = 2: {}", userService.findUserById(2));
        logger.info("find user with id = 2: {}", userService.findUserById(2));

        logger.info("------------------ demo @CacheEvict --------------------");
        userService.clearCache();
        logger.info("find user with id = 1: {}", userService.findUserById(1));
        logger.info("find user with id = 2: {}", userService.findUserById(2));

        logger.info("------------------ demo @CachePut --------------------");
        logger.info("reload and find user with id = 1: {}", userService.reloadAndFindUserById(1));
        logger.info("find user with id = 1: {}", userService.findUserById(1));
        logger.info("find user with id = 2: {}", userService.findUserById(2));
    }
}

Trong ví dụ này mình ko sử dụng thư viện spring-boot-web nên mình sẽ dùng CommandLineRunner để chạy chương trình.

Annotation @EnableCaching là bắt buộc, nó sử dụng để spring context có thể tìm được các annotation @Cacheable, @CacheEvict, @CachePut

2. Demo và giải thích kết quả

Chạy file SpringBootCachingApplication.java

Kết quả:

2020-06-12 16:18:37.221  INFO 14016 --- : ------------------ demo @Cacheable --------------------
2020-06-12 16:18:40.247  INFO 14016 --- : find user with id = 1: User{id=1, name='Any name'}
2020-06-12 16:18:40.253  INFO 14016 --- : find user with id = 1: User{id=1, name='Any name'}
2020-06-12 16:18:43.255  INFO 14016 --- : find user with id = 2: User{id=2, name='Any name'}
2020-06-12 16:18:43.255  INFO 14016 --- : find user with id = 2: User{id=2, name='Any name'}
2020-06-12 16:18:43.255  INFO 14016 --- : ------------------ demo @CacheEvict --------------------
2020-06-12 16:18:46.256  INFO 14016 --- : find user with id = 1: User{id=1, name='Any name'}
2020-06-12 16:18:49.256  INFO 14016 --- : find user with id = 2: User{id=2, name='Any name'}
2020-06-12 16:18:49.256  INFO 14016 --- : ------------------ demo @CachePut --------------------
2020-06-12 16:18:52.258  INFO 14016 --- : reload and find user with id = 1: User{id=1, name='reload Any name'}
2020-06-12 16:18:52.259  INFO 14016 --- : find user with id = 1: User{id=1, name='reload Any name'}
2020-06-12 16:18:52.259  INFO 14016 --- : find user with id = 2: User{id=2, name='Any name'}

Xem log chúng ta thấy:

  • lần đầu tiên gọi hàm findUserById với id = 1 sẽ mất khoảng 3s. Tương tự với id = 2 cũng mất khoảng 3s. Tuy nhiên lần thứ 2 gọi sẽ rất nhanh, chưa đến 1ms (do lấy từ trong cache)
  • Sau khi gọi hàm userService.clearCache() thì hàm findUserById lại tốn khoảng 3s (do cache đã bị xóa và phải thực thi code lại)
  • hàm reloadAndFindUserById(1) sẽ tốn 3s, nhưng sau đó gọi hàm userService.findUserById(1) lại rất nhanh nhưng lại có name mới là 'reload Any Name' chứ không phải là 'Any Name' do cache đã bị ghi đè

Okay, Done!

Download code ví dụ trên tại đây hoặc tại: https://github.com/stackjava/spring-boot-caching

References: https://spring.io/guides/gs/caching/

stackjava.com