Spring Boot 緩存 Cache 入門詳解
一、概述
(一)緩存的必要性
隨著系統(tǒng)訪問量的增加,數(shù)據(jù)庫往往成為性能瓶頸。為了減少數(shù)據(jù)庫的壓力,提高系統(tǒng)的響應(yīng)速度,我們可以通過緩存來優(yōu)化系統(tǒng)性能。
(二)緩存策略
常見的緩存策略包括:
- 讀寫分離:將讀操作分流到從節(jié)點,避免主節(jié)點壓力過多。
- 分庫分表:將讀寫操作分攤到多個節(jié)點,避免單節(jié)點壓力過多。
- 緩存系統(tǒng):使用緩存系統(tǒng)(如 Redis、Ehcache)來存儲經(jīng)常訪問的數(shù)據(jù),減少對數(shù)據(jù)庫的直接訪問。
(三)Spring Cache
Spring 3.1 引入了基于注解的緩存技術(shù),通過 @Cacheable 等注解簡化緩存邏輯,支持多種緩存實現(xiàn)。Spring Cache 的特點包括:
- 通過少量的配置注解即可使得既有代碼支持緩存。
- 支持開箱即用,無需安裝和部署額外第三方組件即可使用緩存。
- 支持 Spring 表達式語言(SpEL),能使用對象的任何屬性或者方法來定義緩存的 key 和 condition。
- 支持 AspectJ,并通過其實現(xiàn)任何方法的緩存支持。
- 支持自定義 key 和自定義緩存管理者,具有相當(dāng)?shù)撵`活性和擴展性。
二、注解
(一)@Cacheable
@Cacheable 注解用于方法上,緩存方法的執(zhí)行結(jié)果。執(zhí)行過程如下:
- 判斷方法執(zhí)行結(jié)果的緩存。如果有,則直接返回該緩存結(jié)果。
- 執(zhí)行方法,獲得方法結(jié)果。
- 根據(jù)是否滿足緩存的條件。如果滿足,則緩存方法結(jié)果到緩存。
- 返回方法結(jié)果。
常用屬性:
cacheNames:緩存名。必填。[]數(shù)組,可以填寫多個緩存名。key:緩存的 key 。允許空。如果為空,則默認方法的所有參數(shù)進行組合。如果非空,則需要按照 SpEL 配置。例如,@Cacheable(value = "users", key = "#id"),使用方法參數(shù)id的值作為緩存的 key 。condition:基于方法入?yún)?,判斷要緩存的條件。允許空。如果非空,則需要按照 SpEL 配置。例如,@Cacheable(condition="#id > 0"),需要傳入的id大于零。unless:基于方法返回,判斷不緩存的條件。允許空。如果非空,則需要按照 SpEL 配置。例如,@Cacheable(unless="#result == null"),如果返回結(jié)果為null,則不進行緩存。
不常用屬性:
keyGenerator:自定義 key 生成器 KeyGenerator Bean 的名字。允許空。如果設(shè)置,則key失效。cacheManager:自定義緩存管理器 CacheManager Bean 的名字。允許空。一般不填寫,除非有多個 CacheManager Bean 的情況下。cacheResolver:自定義緩存解析器 CacheResolver Bean 的名字。允許空。sync:在獲得不到緩存的情況下,是否同步執(zhí)行方法。默認為false,表示無需同步。如果設(shè)置為true,則執(zhí)行方法時,會進行加鎖,保證同一時刻,有且僅有一個方法在執(zhí)行,其它線程阻塞等待。
(二)@CachePut
@CachePut 注解用于方法上,緩存方法的執(zhí)行結(jié)果。與 @Cacheable 不同,它的執(zhí)行過程如下:
- 執(zhí)行方法,獲得方法結(jié)果。也就是說,無論是否有緩存,都會執(zhí)行方法。
- 根據(jù)是否滿足緩存的條件。如果滿足,則緩存方法結(jié)果到緩存。
- 返回方法結(jié)果。
一般來說,@Cacheable 搭配讀操作,實現(xiàn)緩存的被動寫;@CachePut 配置寫操作,實現(xiàn)緩存的主動寫。
(三)@CacheEvict
@CacheEvict 注解用于方法上,刪除緩存。相比 @CachePut,它額外多了兩個屬性:
allEntries:是否刪除緩存名(cacheNames)下,所有 key 對應(yīng)的緩存。默認為false,只刪除指定 key 的緩存。beforeInvocation:是否在方法執(zhí)行前刪除緩存。默認為false,在方法執(zhí)行后刪除緩存。
(四)@Caching
@Caching 注解用于方法上,可以組合使用多個 @Cacheable、@CachePut、@CacheEvict 注解。不太常用,可以暫時忽略。
(五)@CacheConfig
@CacheConfig 注解用于類上,共享如下四個屬性的配置:
cacheNameskeyGeneratorcacheManagercacheResolver
(六)@EnableCaching
@EnableCaching 注解用于標記開啟 Spring Cache 功能,所以一定要添加。
三、Spring Boot 集成
(一)依賴
在 Spring Boot 里,提供了 spring-boot-starter-cache 庫,實現(xiàn) Spring Cache 的自動化配置,通過 CacheAutoConfiguration 配置類。
(二)緩存工具和框架
在 Java 后端開發(fā)中,常見的緩存工具和框架列舉如下:
- 本地緩存:Guava LocalCache、Ehcache、Caffeine 。Ehcache 的功能更加豐富,Caffeine 的性能要比 Guava LocalCache 好。
- 分布式緩存:Redis、Memcached、Tair 。Redis 最為主流和常用。
(三)自動配置
在這些緩存方案當(dāng)中,spring-boot-starter-cache 怎么知道使用哪種呢?在默認情況下,Spring Boot 會按照如下順序,自動判斷使用哪種緩存方案,創(chuàng)建對應(yīng)的 CacheManager 緩存管理器。
private static final Map<CacheType, Class<?>> MAPPINGS;
static {
Map<CacheType, Class<?>> mappings = new EnumMap<>(CacheType.class);
mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);
mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);
mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);
mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);
mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);
mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);
mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);
mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);
mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);
MAPPINGS = Collections.unmodifiableMap(mappings);
}最差的情況下,會使用 SimpleCacheConfiguration。因為自動判斷可能和我們希望使用的緩存方案不同,此時我們可以手動配置 spring.cache.type 指定類型。
四、Ehcache 示例
(一)引入依賴
在 pom.xml 文件中,引入相關(guān)依賴。
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>(二)應(yīng)用配置文件
在 resources 目錄下,創(chuàng)建 application.yaml 配置文件。配置如下:
spring:
cache:
type: ehcache(三)Ehcache 配置文件
在 resources 目錄下,創(chuàng)建 ehcache.xml 配置文件。配置如下:
<ehcache>
<cache name="users"
maxElementsInMemory="1000"
timeToLiveSeconds="60"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>(四)Application
創(chuàng)建 Application.java 類,代碼如下:
@SpringBootApplication
@EnableCaching
public class Application {
}(五)UserDO
在 cn.iocoder.springboot.lab21.cache.dataobject 包路徑下,創(chuàng)建 UserDO.java 類,用戶 DO 。代碼如下:
@TableName(value = "users")
public class UserDO {
private Integer id;
private String username;
private String password;
private Date createTime;
@TableLogic
private Integer deleted;
}(六)UserMapper
在 cn.iocoder.springboot.lab21.cache.mapper 包路徑下,創(chuàng)建 UserMapper 接口。代碼如下:
@Repository
@CacheConfig(cacheNames = "users")
public interface UserMapper extends BaseMapper<UserDO> {
@Cacheable(key = "#id")
UserDO selectById(Integer id);
@CachePut(key = "#user.id")
default UserDO insert0(UserDO user) {
this.insert(user);
return user;
}
@CacheEvict(key = "#id")
int deleteById(Integer id);
}(七)UserMapperTest
創(chuàng)建 UserMapperTest 測試類,我們來測試一下簡單的 UserMapper 的每個操作。核心代碼如下:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class UserMapperTest {
private static final String CACHE_NAME_USER = "users";
@Autowired
private UserMapper userMapper;
@Autowired
private CacheManager cacheManager;
@Test
public void testCacheManager() {
System.out.println(cacheManager);
}
@Test
public void testSelectById() {
Integer id = 1;
UserDO user = userMapper.selectById(id);
System.out.println("user:" + user);
Assert.assertNotNull("緩存為空", cacheManager.getCache(CACHE_NAME_USER).get(user.getId(), UserDO.class));
user = userMapper.selectById(id);
System.out.println("user:" + user);
}
@Test
public void testInsert() {
UserDO user = new UserDO();
user.setUsername(UUID.randomUUID().toString());
user.setPassword("nicai");
user.setCreateTime(new Date());
user.setDeleted(0);
userMapper.insert0(user);
Assert.assertNotNull("緩存為空", cacheManager.getCache(CACHE_NAME_USER).get(user.getId(), UserDO.class));
}
@Test
public void testDeleteById() {
UserDO user = new UserDO();
user.setUsername(UUID.randomUUID().toString());
user.setPassword("nicai");
user.setCreateTime(new Date());
user.setDeleted(0);
userMapper.insert0(user);
Assert.assertNotNull("緩存為空", cacheManager.getCache(CACHE_NAME_USER).get(user.getId(), UserDO.class));
userMapper.deleteById(user.getId());
Assert.assertNull("緩存不為空", cacheManager.getCache(CACHE_NAME_USER).get(user.getId(), UserDO.class));
}
}五、Redis 示例
(一)引入依賴
在 pom.xml 文件中,引入相關(guān)依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>(二)應(yīng)用配置文件
在 resources 目錄下,創(chuàng)建 application.yaml 配置文件。配置如下:
spring:
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
timeout: 0
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1
cache:
type: redis(三)Application
與 4.4 Application 一致。
(四)UserDO
與 4.5 UserDO 一致。差別在于,需要讓 UserDO 實現(xiàn) Serializable 接口。
(五)UserMapper
與 4.6 UserMapper 一致。
(六)UserMapperTest
與 4.7 UserMapperTest 基本一致。
六、面試回答思路和答案
(一)緩存的必要性
問題:為什么需要使用緩存?
回答:隨著系統(tǒng)訪問量的增加,數(shù)據(jù)庫往往成為性能瓶頸。緩存可以減少數(shù)據(jù)庫的壓力,提高系統(tǒng)的響應(yīng)速度。緩存系統(tǒng)(如 Redis、Ehcache)可以存儲經(jīng)常訪問的數(shù)據(jù),減少對數(shù)據(jù)庫的直接訪問。
(二)Spring Cache
問題:什么是 Spring Cache?
回答:Spring Cache 是 Spring 3.1 引入的基于注解的緩存技術(shù),通過 @Cacheable 等注解簡化緩存邏輯,支持多種緩存實現(xiàn)。Spring Cache 的特點包括:通過少量的配置注解即可使得既有代碼支持緩存;支持開箱即用;支持 Spring 表達式語言(SpEL);支持 AspectJ;支持自定義 key 和自定義緩存管理者。
(三)@Cacheable
問題:@Cacheable 注解的使用方法和屬性有哪些?
回答:@Cacheable 注解用于方法上,緩存方法的執(zhí)行結(jié)果。常用屬性包括:cacheNames(緩存名,必填),key(緩存的 key,允許空),condition(基于方法入?yún)?,判斷要緩存的條件),unless(基于方法返回,判斷不緩存的條件)。
(四)@CachePut
問題:@CachePut 注解的作用是什么?
回答:@CachePut 注解用于方法上,緩存方法的執(zhí)行結(jié)果。與 @Cacheable 不同,它的執(zhí)行過程如下:執(zhí)行方法,獲得方法結(jié)果;根據(jù)是否滿足緩存的條件,如果滿足,則緩存方法結(jié)果到緩存;返回方法結(jié)果。
(五)@CacheEvict
問題:@CacheEvict 注解的作用是什么?
回答:@CacheEvict 注解用于方法上,刪除緩存。相比 @CachePut,它額外多了兩個屬性:allEntries(是否刪除緩存名下所有 key 對應(yīng)的緩存),beforeInvocation(是否在方法執(zhí)行前刪除緩存)。
(六)Spring Boot 緩存集成
問題:如何在 Spring Boot 中集成緩存?
回答:在 Spring Boot 中,可以通過引入 spring-boot-starter-cache 依賴來集成緩存。Spring Boot 會自動判斷使用哪種緩存方案,也可以通過 spring.cache.type 手動指定。
(七)Ehcache 和 Redis
問題:Ehcache 和 Redis 的區(qū)別是什么?
回答:Ehcache 是一個純 Java 的進程內(nèi)緩存框架,具有快速、精干等特點,是 Hibernate 中默認的 CacheProvider 。Redis 是一個基于鍵值對的內(nèi)存數(shù)據(jù)庫,支持多種數(shù)據(jù)類型,如字符串、哈希、列表、集合、有序集合等。Ehcache 適合用于本地緩存,Redis 適合用于分布式緩存。
(八)緩存策略
問題:常見的緩存策略有哪些?
回答:常見的緩存策略包括:讀寫分離(將讀操作分流到從節(jié)點,避免主節(jié)點壓力過多),分庫分表(將讀寫操作分攤到多個節(jié)點,避免單節(jié)點壓力過多),緩存系統(tǒng)(使用緩存系統(tǒng)(如 Redis、Ehcache)來存儲經(jīng)常訪問的數(shù)據(jù),減少對數(shù)據(jù)庫的直接訪問)。
(九)緩存擊穿
問題:什么是緩存擊穿?如何解決?
回答:緩存擊穿是指多個線程同時訪問同一個緩存鍵,導(dǎo)致緩存失效,多個線程同時查詢數(shù)據(jù)庫,造成數(shù)據(jù)庫壓力。解決方法包括:使用互斥鎖(如 Redis 的 SETNX 命令),保證同一時刻只有一個線程查詢數(shù)據(jù)庫;使用緩存預(yù)熱,在系統(tǒng)啟動時預(yù)先加載緩存。
(十)緩存穿透
問題:什么是緩存穿透?如何解決?
回答:緩存穿透是指查詢一個不存在的數(shù)據(jù),導(dǎo)致緩存和數(shù)據(jù)庫都沒有命中,直接查詢數(shù)據(jù)庫。解決方法包括:使用布隆過濾器,快速判斷數(shù)據(jù)是否存在;對不存在的數(shù)據(jù)也進行緩存,設(shè)置較短的過期時間。
(十一)緩存雪崩
問題:什么是緩存雪崩?如何解決?
回答:緩存雪崩是指大量緩存同時失效,導(dǎo)致大量請求直接查詢數(shù)據(jù)庫,造成數(shù)據(jù)庫壓力。解決方法包括:使用緩存預(yù)熱,在系統(tǒng)啟動時預(yù)先加載緩存;設(shè)置不同的過期時間,避免大量緩存同時失效。
(十二)緩存的過期策略
問題:緩存的過期策略有哪些?
回答:緩存的過期策略包括:設(shè)置固定過期時間(如 Redis 的 EXPIRE 命令),根據(jù)訪問頻率動態(tài)調(diào)整過期時間,根據(jù)數(shù)據(jù)的重要性動態(tài)調(diào)整過期時間。
(十三)緩存的淘汰策略
問題:緩存的淘汰策略有哪些?
回答:緩存的淘汰策略包括:先進先出(FIFO),最近最少使用(LRU),最不經(jīng)常使用(LFU),隨機淘汰。
(十四)緩存的序列化
問題:緩存的序列化有哪些方式?
回答:緩存的序列化方式包括:Java 序列化,JSON 序列化,Protobuf 序列化。Java 序列化簡單,但性能較差;JSON 序列化性能較好,但需要手動實現(xiàn)序列化和反序列化;Protobuf 序列化性能最好,但需要定義.proto 文件。
(十五)緩存的分布式鎖
問題:如何在分布式環(huán)境下實現(xiàn)緩存的鎖?
回答:在分布式環(huán)境下,可以使用 Redis 的 SETNX 命令實現(xiàn)分布式鎖。SETNX 命令可以保證同一時刻只有一個線程獲取到鎖,從而避免緩存擊穿等問題。
(十六)緩存的事務(wù)
問題:緩存是否支持事務(wù)?
回答:緩存本身不支持事務(wù),但可以通過與數(shù)據(jù)庫事務(wù)結(jié)合來實現(xiàn)。例如,在數(shù)據(jù)庫事務(wù)提交后,更新緩存;在數(shù)據(jù)庫事務(wù)回滾后,回滾緩存。
(十七)緩存的監(jiān)控
問題:如何監(jiān)控緩存的使用情況?
回答:可以使用 Redis 的 INFO 命令監(jiān)控緩存的使用情況,包括內(nèi)存使用、命中率、過期鍵數(shù)等。也可以使用第三方監(jiān)控工具,如 Prometheus 和 Grafana。
(十八)緩存的優(yōu)化
問題:如何優(yōu)化緩存的性能?
回答:優(yōu)化緩存性能的方法包括:選擇合適的緩存策略,使用高效的序列化方式,合理設(shè)置緩存的過期時間和淘汰策略,使用分布式鎖避免緩存擊穿,監(jiān)控緩存的使用情況并及時調(diào)整。
(十九)緩存的命中率
問題:如何計算緩存的命中率?
回答:緩存的命中率可以通過以下公式計算:命中率 = 命中次數(shù) /(命中次數(shù) + 未命中次數(shù))??梢酝ㄟ^監(jiān)控工具獲取命中次數(shù)和未命中次數(shù)。
(二十)緩存的場景
問題:緩存適用于哪些場景?
回答:緩存適用于以下場景:經(jīng)常訪問的數(shù)據(jù),如用戶信息、商品信息等;數(shù)據(jù)變化不頻繁,如配置信息;對實時性要求不高的數(shù)據(jù),如統(tǒng)計信息。
(二十一)緩存的局限性
問題:緩存有哪些局限性?
回答:緩存的局限性包括:緩存數(shù)據(jù)與數(shù)據(jù)庫數(shù)據(jù)不一致,緩存擊穿、緩存穿透、緩存雪崩等問題,緩存的內(nèi)存有限,緩存的序列化和反序列化可能影響性能,緩存的分布式鎖實現(xiàn)復(fù)雜。
(二十二)緩存的未來發(fā)展趨勢
問題:緩存技術(shù)未來的發(fā)展趨勢是什么?
回答:緩存技術(shù)未來的發(fā)展趨勢包括:與數(shù)據(jù)庫的深度融合,支持事務(wù)和一致性;支持更多的數(shù)據(jù)類型和查詢方式;提供更好的性能和擴展性;提供更便捷的監(jiān)控和管理工具。
以上就是對 Spring Boot 緩存 Cache 入門的詳細講解和面試回答思路及答案。希望對初學(xué)者有所幫助,祝大家學(xué)習(xí)愉快!
到此這篇關(guān)于Spring Boot 緩存 Cache 入門的文章就介紹到這了,更多相關(guān)Spring Boot 緩存 Cache內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 在SpringBoot項目中使用JetCache緩存的詳細教程
- Springboot集成Ehcache3實現(xiàn)本地緩存的配置方法
- 詳解Springboot @Cacheable 注解(指定緩存位置)
- springboot整合ehcache和redis實現(xiàn)多級緩存實戰(zhàn)案例
- SpringBoot?Cache?二級緩存的使用
- SpringBoot使用@Cacheable注解實現(xiàn)緩存功能流程詳解
- SpringBoot如何使用@Cacheable進行緩存與取值
- SpringBoot使用@Cacheable時設(shè)置部分緩存的過期時間方式
- 詳解SpringBoot2.0的@Cacheable(Redis)緩存失效時間解決方案
相關(guān)文章
Java實現(xiàn)bmp和jpeg圖片格式互轉(zhuǎn)
本文主要介紹了Java實現(xiàn)bmp和jpeg圖片格式互轉(zhuǎn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
Java將word文件轉(zhuǎn)成pdf文件的操作方法
這篇文章主要介紹了Java將word文件轉(zhuǎn)成pdf文件的操作方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09
Java spring webmvc如何實現(xiàn)控制反轉(zhuǎn)
這篇文章主要介紹了Java spring webmvc如何實現(xiàn)控制反轉(zhuǎn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08

