前言
昨天在開發業務時,打算加入快取層來提高系統響應速度。查詢了一些資料,發現 Spring 的快取功能十分強大!只需要新增少量的程式碼,就可以輕鬆快取方法所返回的物件。這篇文章通過描述一個實際使用例子,介紹 Spring Cache 的使用限制以及注意事項。
環境準備
- Redis 5+
- JDK 1.8+
- Gradle 6+
- 一款你喜愛的 IDE
實踐過程
新增依賴
開啟 build.gradle 檔案,新增 Spring Cache 依賴。
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
建立模型
@Data
@AllArgsConstructor
public class Post implements Serializable {
private Long id;
private String title;
private String content;
}
PS:這裡使用到了 Lombok 外掛,如果不熟悉可先查詢相關資料進行了解。
建立模型倉庫
public interface PostRepository {
Post getById(Long id);
}
@Component
public class PostRepositoryImpl implements PostRepository {
@Override
public Post getById(Long id) {
// 模擬查詢時間
simulateSlowService();
return new Post(100L, "title", "content");
}
private void simulateSlowService() {
try {
Long time = 3000L;
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
編寫控制器
@RestController
public class PostController {
private final PostRepository postRepository;
public PostController(PostRepository postRepository) {
this.postRepository = postRepository;
}
@GetMapping("posts/{id}")
public Post getPostById(@PathVariable("id") Long id) {
return postRepository.getById(id);
}
}
針對一些不容易被修改的資源,如果每次都需要到持久化資料庫中進行查詢,無疑是十分浪費的,體驗也差,下面我們使用 Spring Cache 來改進一波。
使用 Spring Cache
@EnableCaching
@SpringBootApplication
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
新增 @EnableCaching 註解啟動 Spring Cache。
spring:
cache:
type: redis
redis:
host: 127.0.0.1
port: 6379
這裡用 Redis 作為快取引擎,如果小夥伴想用其他引擎,可自行查閱文件進行配置。
@RestController
public class PostController {
private final PostRepository postRepository;
public PostController(PostRepository postRepository) {
this.postRepository = postRepository;
}
@Cacheable(cacheNames = "getPostById", key = "#id")
@GetMapping("posts/{id}")
public Post getPostById(@PathVariable("id") Long id) {
return postRepository.getById(id);
}
}
使用 @Cacheable 註解 getPostById 方法,使用了 cacheNames 和 key 引數。這裡先不展開說,下面會集中梳理幾種註解以及它們的引數意義。
Spring Cache 註解
Spring Cache 常用的 5 個註解,分別是:
- @EnableCaching
- @Cacheable
- @CachePut
- @CacheEvict
- @CacheConfig
@EnableCaching
在啟動類新增 @EnableCaching 註解讓系統開啟快取功能。
@Cacheable
功能是開啟快取,可以標記在類上或者是方法上。在呼叫方法時,會先從快取中獲取結果,若不存在再執行方法。主要引數包括 cacheNames、key、condition 和 unless 等。
- cacheNames:用於指定快取儲存的集合名,必須填寫。
- key:快取物件儲存在集合中的 key 值,預設按照函式的所有引數組合作為 key 值。
- condition:快取物件的條件,需使用 SpEL 表示式。只有滿足表示式條件的內容才會被快取。
- unless:快取物件的條件,需使用 SpEL 表示式。它不同於 condition 引數的地方在於它的判斷時機,該條件是在函式被呼叫之後才做判斷的,所以它可以通過對返回物件進行判斷。
- keyGenerator:用於指定 key 生成器。若需要自定義 key 生成器,需要實現 KeyGenerator 介面,並使用該引數來指定。
- cacheManager:用於指定使用哪個快取管理器。
- cacheResolver:用於指定使用那個快取解析器。
@CachePut
針對方法配置,與 @Cacheable 不同的地方在於它每次都會觸發真實方法的呼叫。簡單來說就是更新快取資料。主要引數和 @Cacheable 一致。
@CacheEvict
針對方法配置,用來從快取中移除相應資料。除了與 @Cacheable 相同的引數以外,還有 allEntries 和 beforeInvocation。
- allEntries 非必須,預設為 false。當為 true 時,會移除所有資料。
- beforeInvocation 非必須,預設為 false,會在呼叫方法之後移除資料。當為 true 時,會在呼叫方法之前移除資料。
@CacheConfig
該註解是一個類級註解,可以讓類下面的方法共享 cacheNames、keyGenerator、cacheManager 和 cacheResolver 引數。
自定義 cacheNames
這裡是為了讓我們的快取註解支援自定義 TTL 失效時間,類似下面這種效果。
// 3600 秒後快取集合自動過期
@Cacheable(cacheNames = "getPostById#3600", key = "#id")
為了實現這種效果,我們建立一個 CustomRedisCacheManager 自定義類,如下所示。
public class CustomRedisCacheManager extends RedisCacheManager {
public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
String[] array = StringUtils.delimitedListToStringArray(name, "#");
name = array[0];
if (array.length > 1) {
long ttl = Long.parseLong(array[1]);
cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl));
}
return super.createRedisCache(name, cacheConfig);
}
}
使用自定義 CustomRedisCacheManager 配置 CacheConfig。
public class CacheConfig extends CachingConfigurerSupport {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private Integer redisPort;
@Value("${spring.redis.database}")
private Integer redisDatabase;
@Override
@Bean
public CacheManager cacheManager() {
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))
.computePrefixWith(cacheName -> "caching:" + cacheName);
return new CustomRedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory()), defaultCacheConfig);
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(redisHost);
configuration.setPort(redisPort);
configuration.setDatabase(redisDatabase);
return new LettuceConnectionFactory(configuration);
}
}
總結
本文主要介紹了 Spring Cache 的基本使用方式和常用註解。後續文章準備深入瞭解其底層原理。
歡迎大家關注我的公眾號「是然的筆記本」。
再會!