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:
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 cacheuser
- 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àmuserService.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/