spring缓存
org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口是spring中用来统一不同的缓存技术公用接口
- Cache接口为缓存的组件规范定义,包含缓存的各种操作集合
- Cache接口下spring提供了各种xxxCache的实现,如 RedisCache、ConcurrentMapCache等
- 每次调用有缓存功能的方法时,spring 会检查指定参数的目标方法是否已经被调用过,如果有就直接从缓存中获取方法的结果;如果没有就调用过方法就先缓存结果后在将结果返回给用户,下次调用直接从缓存中获取。
-
使用spring缓存抽象时我们需要关注以下两点:
- 确定方法需要被缓存以及他们的缓存策略
- 从缓存中读取之前缓存存储的数据
spring 缓存中几个概念
名词 | 说明 |
---|---|
Cache | 缓存接口,定义缓存操作。实现有: 等 |
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存的注解 |
@CachePut | 保证方法被调用,又希望结果被缓存,通常用于缓存更新 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
@Cacheable、@CacheEvict和@CachePut
注解属性
属性 | 说明 | 示例 |
---|---|---|
value | 缓存的名称,在 spring 配置文件中定义,必须指定 至少一个 | @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
key | 缓存的 key,可以为空,如果指定要必须按照 spel 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | @Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 spel 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在调用方法之前之后都能判断 | @Cacheable(value=”testcache”,condition=”#userNam e.length()>2”) |
allEntries (@CacheEvict ) | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 | @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation(@CacheEvict) | 是否在方法执行前就清空,缺省为 false,如果指定 为 true,则在方法还没有执行的时候就清空缓存, 缺省情况下,如果方法执行抛出异常,则不会清空 缓存 | @CachEvict(value=”testcache”, beforeInvocation=true) |
unless(@CachePut) (@Cacheable) | 用于否决缓存的,不像condition,该表达式只在方 法执行之后判断,此时可以拿到返回值result进行判 断。条件为true不会缓存,fasle才缓存 | @Cacheable(value=”testcache”,unless=”#result == null”) |
SpEL 表达式
作用 | 表达式 |
---|---|
当前被调用的方法名 | #root.methodName |
当前被调用的方法 | #root.method.name |
当前被调用的目标对象 | #root.target |
当前被调用的目标对象类 | #root.targetClass |
当前被调用的方法的参数列表 | #root.args[0] |
方法参数的名字 | 可以直接 #参数名,也可以使用 #p0或#a0,0代表参数的索引 |
方法执行后的返回值 | #result |
spring 缓存使用
- 引入 starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
- 配置注解 @EnableCaching
@EnableCaching
@SpringBootApplication
public class SpringBootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootCacheApplication.class, args);
}
}
- 使用缓存注解
@Cacheable、@CacheEvict和@CachePut
@Cacheable(cacheNames = {"emp"})
public Emp queryEmp(Integer id) {
System.out.println("查询员工ID: " + id);
Emp emp = empMapper.queryEmpById(id);
return emp;
}
当我们在浏览器输入 http://localhost:8080/query/1
时,控制台日志会打印日志
说明此时查询成功,但是当我再次刷新页面时控制台不打印日志,单页面能正常得到结果,说明缓存起作用了。
@Cacheable
将方法的结果进行缓存,以后在需要数据时,直接在缓存中获取,不会再调用方法
@Cacheable的几个属性
- cacheNames : 缓存的名字
- key : 该 cacheNames 缓存中的使用的key值,不指定时默认使用方法的参数,也可以使用 sepl 表达式来指定其值,比如:key="#id" 表示 queryEmp 方法的参数 id 的值,同时 "#id" 等价于 "#"a0、"#p0"、#root.args[0]
- keyGenerator : 指定key的生成器。key / keyGenerator 二选一使用,不要同时使用
- cacheManager : 指定从哪个缓存管理器中取,比如在 RedisCache 还是ConcurrentMapCache 中取值
- condition : 指定缓存条件,满足某些条件后才缓存方法结果,也可以是 spel 表达式比如:condition = "#id > 0",当第一个参数值大于1时才缓存
- unless : 否定缓存,当 unless 指定的条件返回 true 是,方法结果就不会被缓存比如:unless = "#result == null" 意思是方法返回结果为空,不进行缓存
缓存的工作原理
- 自动配置类
CacheAutoConfiguration
- 通过
CacheConfigurationImportSelector
类给容器中配置了其他的缓存配置,比如:
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
默认使用 SimpleCacheConfiguration
,它的作用是什么呢?
@Configuration
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
private final CacheProperties cacheProperties;
private final CacheManagerCustomizers customizerInvoker;
SimpleCacheConfiguration(CacheProperties cacheProperties,
CacheManagerCustomizers customizerInvoker) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
}
@Bean
public ConcurrentMapCacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
}
可以看到给容器中注入了一个 ConcurrentMapCacheManager 的缓存管理器。在 ConcurrentMapCacheManager 中我发现一个 ConcurrentHashMap,由此可以知道spring默认缓存的本质利用的map来缓存的。
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
}
@CachePut
调用注解的方法,又去更新缓存数据。使用场景,修改数据库的某些数据的同时更新缓存数据。这个注解的运行时机先运行目标方法,再将结果缓存起来。
@CachePut(value = "emp",key = "#emp.id")
public Employee updateEmp(Emp emp){
System.out.println("updateEmp:"+emp);
empMapper.updateEmp(employee);
return emp;
}
@CacheEvict
清除缓存中的数据
/**
* @CacheEvict:缓存清除
* key:指定要清除的数据
* allEntries = true:指定清除这个缓存中所有的数据
* beforeInvocation = false:缓存的清除是否在方法之前执行
* 默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
*
* beforeInvocation = true:
* 代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
*
*
*/
@CacheEvict(value="emp",beforeInvocation = true/*key = "#id",*/)
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
empMapper.deleteEmpById(id);
}
@Caching 定义复杂的缓存规则
@Caching(
cacheable = {
@Cacheable(value="emp",key = "#lastName")
},
put = {
@CachePut(value="emp",key = "#result.id"),
@CachePut(value="emp",key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return empMapper.getEmpByLastName(lastName);
}
这段代码的意思是:我们根据lastName查询的结果会放到缓存中,同时也会以id,email为key来缓存数据的。但是需要注意一点,使用lastName查询时,方法还是会执行,因为我们使用@CachePut注解,这个注解要求目标方法一定要执行的。
@CachingConfig
抽象公共的缓存配置,什么意思?比如我们案例中所有的缓存都是emp,就是value='emp'
这个指定,那么每个方法都指定很费劲,此时可以使用 @CachingConfing
注解
@CacheConfig(cacheNames="emp")
@Service
public class EmployeeService {
}
使用缓存中间件 redis
- 引入 starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId></dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
- 修改 yml 配置文件
spring:
redis:
host: 127.0.0.1
redis 使用
在 RedisAutoConfiguration
类中帮我自动注入了RedisTemplate
和StringRedisTemplate
,也就是说我们可以直接使用它们进行redis的日常操作了,一般使用RedisTemplate
就可以了。
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootCacheApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testSetRedis() {
Emp emp = new Emp();
emp.setId(4L);
emp.setName("kate");
emp.setDeptId(1L);
emp.setEmail("SAJJee@qq.com");
emp.setSex(0);
redisTemplate.opsForValue().set("emp:4", emp);
//其他常用的操作函数
//redisTemplate.opsForHash();
//redisTemplate.opsForList();
//redisTemplate.opsForSet();
//redisTemplate.opsForZSet();
}
}
redis 数据存储序列化问题
org.springframework.data.redis
中提供的序列化方式有
GenericJackson2JsonRedisSerializer
GenericToStringSerializer
Jackson2JsonRedisSerializer
JdkSerializationRedisSerializer
OxmSerializer
StringRedisSerializer
使用默认的序列化方式:
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testSetRedis() {
Emp emp = new Emp();
emp.setId(4L);
emp.setName("kate");
emp.setDeptId(1L);
emp.setEmail("SAJJee@qq.com");
emp.setSex(0);
redisTemplate.opsForValue().set("emp:4", emp);
}
执行结果:
自定义序列化:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Emp> empRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<String, Emp> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Emp> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Emp.class);
//设置key的序列化方式
template.setKeySerializer(new StringRedisSerializer());
//设置value的序列化方式
template.setValueSerializer(jsonRedisSerializer);
return template;
}
}
测试用例:
//注入自定义序列化方式模板
@Autowired
private RedisTemplate<String, Emp> empRedisTemplate;
@Test
public void testSetRedis() {
Emp emp = new Emp();
emp.setId(4L);
emp.setName("阿江");
emp.setDeptId(1L);
emp.setEmail("SAJJee@qq.com");
emp.setSex(0);
empRedisTemplate.opsForValue().set("emp:4", emp);//操作字符串
}
执行结果:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。