SpringCache小记
# Spring Cache 小记
官方文档:https://springdoc.cn/spring-cache-tutorial/ (opens new window)
# 基础知识
# 常用注解
@EnableCaching
:开启缓存功能,一般放在启动类上。
@Cacheable
:表示该方法支持缓存。当调用被注解的方法时,如果对应的键已经存在缓存,则不再执行方法体,而从缓存中直接返回。当方法返回 null 时,将不进行缓存操作。(一般用于查询方法上)@CachePut
:表示执行该方法后,其值将作为最新结果更新到缓存中,每次都会执行该方法。(一般用于新增方法上)@CacheEvict
:表示执行该方法后,将触发缓存清除操作。(一般用于更新或删除方法上)@Caching
:用于组合前三个注解,例如:@Caching(cacheable = @Cacheable("CacheConstant.GET_USER"), evict = {@CacheEvict("CacheConstant.GET_DYNAMIC", allEntries = true)} public User find(Integer id) { return null; } // 使用参数 allEntries 与要清空的缓存结合使用;这将清除缓存 CacheConstant.GET_DYNAMIC 中的所有条目。 // cacheable -- @Cacheable // put -- @CachePut // evict -- @CacheEvict
1
2
3
4
5
6
7
8
9
# 常用注解属性
value
/cacheNames
:缓存名称(必填),指定缓存的命名空间;key
:用于设置在命名空间中的缓存 key 值,默认使用方法参数值,也可以使用 SpEL 表达式定义;keyGenerator
:缓存 Key 的生成策略,它和key
属性互斥使用(只能二选一)unless
:条件符合则不缓存;使用 unless 时可以在调用的方法获取到结果之后再进行判断(如
#result == null
,表示如果结果为 nu11 时不缓存)condition
:条件符合则缓存;cacheManager
:指定使用的缓存管理器;cacheResolver
:作用和cacheManager
属性一样(只能二选一)sync
:是否使用同步模式。若使用同步模式,在多个线程同时对一个 key 进行 load 时,其他线程将被阻塞。默认值是false
。
缓存同步模式:
sync 开启或关闭,在
Cache
和LoadingCache
中的表现是不一致的:
- Cache 中,sync 表示是否需要所有线程同步等待
- LoadingCache 中,sync 表示在读取 不存在/已驱逐 的 key 时,是否执行被注解方法
# 清理全部缓存
通过 allEntries
、beforeInvocation
属性可以来清除全部缓存数据,
- 不过
allEntries
是方法调用后清理, beforeInvocation
是方法调用前清理。
# SpEL表达式
SpEL 表达式的语法
Spring Cache 可用的变量
# Caffeine 缓存设置及代码实践
# 1. 引入依赖
<!-- 引入Caffeine缓存依赖 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
2
3
4
5
注释:选择使用 Caffeine 作为缓存依赖,是因为它是一款性能卓越的本地缓存库,具有快速、高效和可配置的特点。
# 2. 编写配置类
@Configuration
@EnableCaching
public class CacheConfig {
/**
* 配置Caffeine缓存管理器
*/
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
// 设置缓存过期时间为30分钟
.expireAfterWrite(30, TimeUnit.MINUTES)
// 设置最大缓存条目数
.maximumSize(1000)
.recordStats()
);
return cacheManager;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3. 编写字典业务类
@Service
public class DictionaryService {
@Resource
private DocumentExpiredMapper documentExpiredMapper;
/**
* 通过@Cacheable注解将字典数据存入缓存
* @param dictionaryType 字典类型
* @return 字典数据的HashMap
*/
@Cacheable(value = "dictionaryCache", key = "#dictionaryType")
public HashMap<String, String> getDict(String dictionaryType) {
List<DictDTO> dictList = documentExpiredMapper.getDict(dictionaryType);
HashMap<String, String> dictMap = new HashMap<>(16);
for (DictDTO dictDTO : dictList) {
dictMap.put(dictDTO.getDictValue(), dictDTO.getDictName());
}
return dictMap;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 注解解析
@Cacheable
是 Spring 框架中用于声明一个方法要被缓存处理的注解。在上面这段代码中,它的用法是为 getDict
方法添加了缓存的支持。
具体到这个注解的属性解释如下:
value
:指定缓存的名称,可以理解为给缓存起一个名字,这样你可以在不同地方使用相同名字的缓存。在你的例子中,缓存的名称是 "dictionaryCache"。key
:指定缓存的键。缓存的键决定了缓存的唯一性。在你的例子中,"#dictionaryType"
表示缓存的键是方法的参数dictionaryType
的值。这样就能够通过不同的dictionaryType
值来区分不同的缓存数据。
综合起来,@Cacheable(value = "dictionaryCache", key = "#dictionaryType")
的含义是:
- 这个方法的执行结果将会被缓存起来。
- 缓存的名称是 "dictionaryCache"。
- 缓存的键是方法的参数
dictionaryType
的值。
流程解析
当这个方法被调用时,Spring 会先检查缓存中是否已经存在了相同键的缓存数据,如果存在就直接返回缓存的结果,而不执行方法体;如果不存在,就执行方法体,并将方法的结果缓存起来,以备后续相同参数的方法调用直接使用缓存数据,提高执行效率。这样,对于相同的 dictionaryType
参数,方法的执行结果只需要计算一次,之后就可以直接从缓存中获取,避免了重复计算。
# 4. 编写抽象字典转换器
public abstract class AbstractDictConverter implements Converter<String> {
/**
* 获取字典类型
* 使用protected修饰符可以将方法或变量限制在同一包中的类和子类中访问。
* @return
*/
protected abstract String getDictType();
@Override
public Class<?> supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
@Override
public String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
return cellData.getStringValue();
}
@Override
public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {
DictionaryService bean = SpringUtil.getBean(DictionaryService.class);
HashMap<String, String> dictMap = bean.getDict(getDictType());
String convertedValue = dictMap.get(context.getValue());
if (convertedValue != null) {
return new WriteCellData<>(convertedValue);
} else {
return new WriteCellData<>(context.getValue());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
注意事项:
- 因为转换器是 new 出来的,所以需要从代码中获取 bean(
SpringUtil.getBean()
)
# 5. 继承使用
第一个转换器:
public class AlertTypeConvert extends AbstractDictConverter {
@Override
protected String getDictType() {
return ALERT_TYPE;
}
}
2
3
4
5
6
7
8
第二个转换器:
public class DocumentDictConverter extends AbstractDictConverter {
@Override
protected String getDictType() {
return CERTIFICATE;
}
}
2
3
4
5
6
7
8
# 注意事项
💡 注意:在线上环境可能会遇到问题,比如测试环境正常运行,但在生产环境中无法进行字典转换,可能导致转换失败。为了解决这个问题,我们可以考虑使用自定义的静态变量 HashMap 来实现该功能,确保字典数据在各个环境中都能正确加载。
其他实现方式阅读:
文件导出之自定义字典映射转换器 (opens new window)