启用缓存
@SpringBootApplication
@EnableCaching
public class SpringCacheApp {
public static void main(String[] args) {
SpringApplication.run(Cache.class, args);
}
}
默认情况下,@EnableCaching将注册一个ConcurrentMapCacheManager的Bean储存在jvm的内存中,ConcurrentMapCacheManager将值存储在ConcurrentHashMap的实例中
使用Redis作为缓存管理器
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory();
}
@Bean
public CacheManager cacheManager() {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.disableCachingNullValues()
.serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory())
.cacheDefaults(redisCacheConfiguration)
.build();
return redisCacheManager;
}
}
使用注解操作缓存
@Cacheable
将方法运行的结果进行缓存, 在缓存时效内再次调用该方法时不会调用方法本身,而是直接从缓存获取结果并返回给调用方.
| 属性名 | 描述 |
|---|---|
| value | 指定缓存的名称,Spring Cache 使用 CacheManager 管理多个缓存组件 Cache,此处的 Cache 组件就是根据名称进行区分的,它负责对缓存执行真正的 CRUD |
| key | 缓存数据时Key的值,默认是使用方法参数值,可以使用SpEL表达式计算Key值 |
| keyGenerator | 缓存 Key 的生成策略,和 Key 属性互斥 |
| cacheManager | 指定缓存管理器 |
| cacheResolver | 作用和 cacheManager 属性一样,二者二选一 |
| condition | 指定缓存的条件(满足什么条件才缓存),可用 SpEl 表达式(如: #id > 0, 表示当入参id大于0时才缓存) |
| unless | 是否缓存,即满足 unless 指定的条件时,方法的结果不进行缓存,使用 unless 时可以在调用的方法获取到结果之后再进行判断(如 null == #result,表示结果为 null 时不缓存) |
| sync | 是否使用异步模式进行缓存,默认是false |
示例: 缓存 getById() 方法的结果
@Service
public class MyService{
@Resource
private MyRepository repository;
@Cacheable(value = "myCache", key = "#id")
public MyEntity getById(Long id) {
return repository.findById(id).orElse(null);
}
}
@CachePut
@CachePut用于更新缓存数据,相当于缓存使用的是写模式中的双写模式 示例
@Service
public class MyService {
@Resource
private MyRepository repository;
@CachePut(value = "myCache", key = "#entity.id")
public void saveEntity(MyEntity entity) {
repository.save(entity);
}
}
@CacheEvict
删除缓存数据,相当于缓存使用的是写模式中的失效模式
| 属性名 | 描述 |
|---|---|
| value/cacheNames | 缓存的名称 |
| key | 缓存的键 |
| allEntries | 是否根据缓存名称清空所有缓存数据,默认值为 false ,当值指定为 true 时,Spring Cache 将忽略注解上指定的 key 属性 |
| beforeInvovation | 是否在执行方法前清空缓存,默认为 false |
示例
@Service
public class MyService {
@Resource
private MyRepository repository;
@CacheEvict(value = "myCache", key = "#id")
public void deleteEntityById(Long id) {
repository.deleteById(id);
}
}
@Caching
用于指定多个 Spring Cache 相关的注解
| 属性名 | 描述 |
|---|---|
| cacheable | 用于指定 @Cacheable 注解 |
| put | 用于指定 @CachePut 注解 |
| evict | 用于指定 @CacheEvict 注解 |
当调用save方法时,Spring会根据 @CacheEvict 注解先从 thirdCache 缓存中移除数据。然后,Spring 将执行该方法并将结果保存到数据库或外部 API。
方法执行后,Spring 会根据@CachePut注解将结果添加到 myCache 和 secondCache 缓存中。Spring 还将根据@Cacheable 注解检查结果是否已缓存在 fourthCache 和 fifthCache 缓存中。如果结果尚未缓存,Spring 会将结果缓存在适当的缓存中。如果结果已经被缓存,Spring 将返回缓存的结果,而不是再次执行该方法。
@Service
public class MyService {
@Caching(
put = {
@CachePut(value = "myCache", key = "#result.id"),
@CachePut(value = secondCache", key = "#result.id")
},
evict = {
@CacheEvict(value = "thirdCache", key = "#id")
},
cacheable = {
@Cacheable(value = "fourthCache", key = "#id"),
@Cacheable(value = "fifthCache", key = "#result.id")
}
)
public MyEntity save(Long id, String name) {
MyEntity entity = new MyEntity(id, name);
return entity;
}
}
@CacheConfig
将一些缓存配置简化到类级别的一个地方,这样我们就不必多次声明相关值
@CacheConfig(cacheNames={"myCache"})
@Service
public class MyService {
@Resource
private MyRepository repository;
@Cacheable(key = "#id")
public MyEntity getById(Long id) {
return repository.findById(id).orElse(null);
}
@CachePut(key = "#entity.id")
public void save(MyEntity entity) {
repository.save(entity);
}
@CacheEvict(key = "#id")
public void deleteById(Long id) {
repository.deleteById(id);
}
}
Condition & Unless
- condition: 满足条件时缓存, 可用SpEL表达式(如 #id>0,表示当入参 id 大于 0 时才缓存)
- unless: 满足条件时不缓存, 可在调用的方法获取到结果之后再进行判断(如 #result == null,表示如果结果为 null 时不缓存)
// id > 10 缓存
@CachePut(key = "#entity.id", condition="#entity.id > 10")
public void save(MyEntity entity) {
repository.save(entity);
}
// 结果为 null 时不缓存
@CachePut(key = "#id", condition="#result == null")
public void save(MyEntity entity) {
repository.save(entity);
}
清理全部缓存 (allEntries、beforeInvocation)
- allEntries 方法调用后清理
- beforeInvocation 方法调用前清理
//方法调用完成之后,清理所有缓存
@CacheEvict(value="myCache",allEntries=true)
public void delectAll() {
repository.deleteAll();
}
//方法调用之前,清除所有缓存
@CacheEvict(value="myCache",beforeInvocation=true)
public void delectAll() {
repository.deleteAll();
}
SpEL表达式的语法
| 类型 | 运算符 |
|---|---|
| 算术 | <, >, <=, >=, ==, !=, lt, gt, le, ge, eq, ne |
| 关系 | +, -, *, /, %, ^ |
| 逻辑 | &&, ||, !, and, or, not, between, instanceof |
| 条件 | ?:(ternary), ?:(elvis) |
| 正则表达式 | matches |
| 其它 | ?., ?[...], ![...], ^[...], $[...] |
Spring Cache 可用的变量
| 类型 | 位置 | 运算符 | 示例 |
|---|---|---|---|
| methodName | 根对象 | 要调用的方法的名称 | #root.methodName |
| method | 根对象 | 正在调用的方法 | #root.method.name |
| target | 根对象 | 正在调用的目标对象 | #root.target |
| targetClass | 根对象 | 要调用的目标类 | #root.targetClass |
| args | 根对象 | 用于调用目标的参数(作为数组) | #root.args[0] |
| caches | 根对象 | 正在调用的方法使用的缓存队列(如 @Cacheable(value={"cache1","cache2"}),则有两个缓存) | #root.caches[0].name |
| argument name | 评估背景 | 方法参数名,可以直接使用 #参数名 ,也可以使用 #p0 或 #a0 的形式, 0 代表参数的索引 | #a0, #p0 |
| result | 评估背景 | 方法执行后的返回值,仅当方法执行之后的判断有效, 如unless、 cache put、cache evict (当 beforeInvocation = false)的表达式 | #root |
使用场景
- 只缓存经常读取的数据,很少或从不访问的的缓存数据会占用内存资源,从而导致性能问题
- 根据程序特定需求选择合适的缓存提供程序(Ehcache, Hazelcast 和 Redis)和策略
- 使用缓存时注意线程安全问题,并发情况下可能存在缓存不一致问题,必要时使用适当的同步机制
- 使用适当的缓存键设计。缓存键对于每个数据项都应该是唯一的,并且应该考虑可能影响缓存数据的任何相关参数,例如用户 ID、时间或位置
- 读多写少、即时性与一致性要求不高的数据 可以使用 Spring Cache
- 读多写多、即时性与一致性要求非常高的数据, 不能使用 Spring Cache, 建议考虑特殊的设计(例如使用 Cancal 中间件)