【Java】Spring Cache 缓存
Spring Cache
一、Spring缓存抽象
Spring从3.1开始定义了 org.springframework.cache.Cache
和 org.springframework.cache.CacheManager
接口来统一不同的缓存技术; 并支持使用 JCache(JSR-107)
注解简化我们开发。
常用的缓存实现有 RedisCache
、EhCacheCache
、ConcurrentMapCache
等。
JCache (JSR107)
Java Caching定义了5个核心接口,分别是 CachingProvider, CacheManager, Cache, Entry 和 Expiry。
- CachingProvider:定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
- CacheManager:定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
- Cache:是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
- Entry:是一个存储在Cache中的key-value对。
- Expiry:每一个存储在Cache中的条目有一个定义的有效期,即 Expiry Duration。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
二、Spring Cache 注解
@EnableCaching
启用基于 Spring 注解的缓存功能。
@Configuration
@EnableCaching
public class AppConfig {
@Bean
public CacheManager cacheManager() {
// 配置并返回Spring的CacheManager SPI的实现
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
return cacheManager;
}
}
@CacheConfig
主要用于统一配置该类中会用到的一些共用的缓存配置。
在类级别声明 @CacheConfig
后,就可以不用每个 @Cacheable
都设置 value
属性,省事多了。当然,如果在方法上写了别的值,那依然以方法上的值为准。
- 参数:
参数名 | 类型 | 描述 |
---|---|---|
cacheNames | String[] |
在类级别定义缓存操作要使用的默认缓存的名称。如果在操作级别未设置任何值,则使用这些值而不是默认值。 |
keyGenerator | String | 用于指定key生成器,非必需。可以指定一个自定义的key生成器,需要实现 org.springframework.cache.interceptor.KeyGenerator 接口,并使用该参数来指定。该参数与key是互斥的。 |
cacheManager | String | 用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用。 |
cacheResolver | String | 用于指定使用那个缓存解析器,非必需。需通过 org.springframework.cache.interceptor.CacheResolver 接口来实现自己的缓存解析器,并用该参数指定。 |
@Service
@CacheConfig(cacheNames = {"taskLog"})
public class TaskLogService {
@CachePut(key = "#tasklog.id")
public boolean create(Tasklog tasklog){
...
return true;
}
@Cacheable(key = "#id")
public Tasklog findById(String id){
...
return taskLogMapper.selectById(id);
}
}
@Cacheable
主要方法返回值加入缓存。同时在查询时,会先从缓存中取,若不存在才再发起对数据库的访问。
- 主要参数:
参数名 | 类型 | 描述 |
---|---|---|
value | String[] |
cacheNames 的别名 |
cacheNames | String[] |
指定缓存的名称,用于确定目标缓存(或多个缓存),匹配特定bean定义的限定符值或bean名称。如果没有在 @CacheConfig 中指定,则必须要这里指定至少一个。 |
key | String |
缓存的 key,为空则按照指定的 key 生成器生成,或使用缺省的方法生成(所有参数进行组合)。如果指定,要按照 SpEL 表达式编写。 |
condition | String |
是否使用缓存的条件 SpEL 表达式,返回 true 才进行缓存(在方法执行之前进行评估)。可以为空,表示方法结果始终缓存。 |
unless | String |
用来决定是否更新缓存的 SpEL 表达式,如果返回 false 才放入缓存。与 condition 不同,此表达式是在调用方法之后计算的,因此可以引用结果。默认值为空,表示缓存永远不会被否决。 |
sync | boolean |
缓存的同步。在多线程环境下,某些操作可能使用相同参数同步调用。默认情况下,缓存不锁定任何资源,可能导致多次计算,而违反了缓存的目的。对于这些特定的情况,属性 sync 可以指示底层将缓存锁住,使只有一个线程可以进入计算,而其他线程堵塞,直到返回结果更新到缓存中。 |
其它参数参考 @CacheConfig
。
- 示例:
@Cacheable(cacheNames={"book"}, key="#name", condition="#name.length < 32")
public Book findBook(String name) { ... }
@CachePut
配置于函数上,能够根据参数定义条件进行缓存,与@Cacheable不同的是,每次回真实调用函数,所以主要用于数据新增和修改操作上。
@CacheEvict
配置于函数上,通常用在删除方法上,用来从缓存中移除对应数据。
- 主要参数:
参数名 | 类型 | 描述 |
---|---|---|
value | String[] |
cacheNames 的别名 |
cacheNames | String[] |
同 @Cacheable |
key | String |
同 @Cacheable |
condition | String |
同 @Cacheable |
allEntries | boolean |
是否清空所有缓存内容,缺省为 false ,如果指定为 true ,则方法调用后将立即清空所有缓存。请注意,不允许将此参数设置为 true 并指定 key 。 |
beforeInvocation | boolean |
是否在方法执行前就清空,缺省为 false ,如果指定为 true ,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存。 |
- 示例:
@CacheEvict(cacheNames="books", allEntries=true)
public void loadBooks(InputStream batch) { ... }
@Caching
配置于函数上,组合多个Cache注解使用。
比如用户新增成功后,我们要添加id–>user;username—>user;email—>user的缓存;此时就需要@Caching组合多个注解标签了。
@Caching(put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
})
public User save(User user) { ... }
自定义缓存注解
比如上面的那个@Caching组合,会让方法上的注解显得整个代码比较乱,此时可以使用自定义注解把这些注解组合到一个注解中,如:
@Caching(put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
})
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface UserSaveCache {
}
这样我们在方法上使用如下代码即可,整个代码显得比较干净。
@UserSaveCache
public User save(User user) { ... }
SpEL 表达式介绍
自定义 Key 生成器
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName() + "[" + Arrays.asList(params).toString() + "]";
}
};
}
}
二、Java 常用 Redis 库
-
Spring Data Redis 可能是将Redis集成到您的Spring应用程序中最简单,最快的方法,它为Redis提供了Spring Data平台的抽象。
-
Jedis 称自己为“一个极小而理智的Redis Java客户端” ,其构想是考虑了简单性和易用性
-
Reddison是Redis Java客户端,具有内存数据网格功能以及30多种可用的对象和服务 。 该库基于高性能的异步和无锁Java Redis客户端和Netty框架,在Codota的用户群中仅拥有8%的用户。优点是提供了很多redis的分布式操作和高级功能,缺点是api抽象,学习成本高。
-
Lettuce是一个完全无阻塞的Redis客户端,也是使用Netty框架构建的,它提供了反应式,异步和同步数据访问 。 根据我们的统计,这是5%开发人员的选择。优点是提供了很多redis高级功能,例如集群、哨兵、管道等,缺点是api抽象,学习成本高。
-
Jedis是Redis官方推荐的面向Java的操作Redis的客户端,而RedisTemplate,在SpringBoot1.x时是SpringDataRedis中对JedisApi的高度封装,而到了SpringBoot2.x,具体实现变成了 Lettuce。
结论
跟 SpringBoot 走,用 Spring Data Redis。