六、Spring Boot与缓存
1、Spring缓存抽象
Spring定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术,并支持使用JCache(JSR-107)注解简化我们的开发
2、几个重要概念和缓存注解
组件和注解 | 功能 |
---|---|
Cache | 缓存接口,定义缓存操作,实现有: RedisCache、EhCacheCache、ConcurrentMapCache 等 |
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 主要对方法进行配置,能够根据方法的请求参数对其结果继续缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用、又希望结果被缓存 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
3、Spring Boot缓存的使用
-
搭建基本环境、数据库表、javabean封装数据
-
整合mybatis操作数据库,关于整合mybatis可以看之前的是Spring Boot与数据访问
-
开启基于注解的缓存
@EnableCaching //开启缓存 @MapperScan(value = "com.cache.springboot.mapper") @SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(SpringbootApplication.class, args); } }
//将方法的结果进行缓存,以后再要相同的数据,直接从缓存中获取,不会调用方法 @Cacheable(cacheNames = "share") public Share getShareById(Integer id){ Share shareById = shareMapper.getShareById(id); return shareById; }
我们可以查看@Cacheable里的属性
-
cacheNames/value:指定缓存组件的名字
关于缓存组件的名字:CacheManager管理多个Cache组件,对缓存真正的CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字
我们可以传入数组,将数据放入多个缓存里边
-
key:缓存数据使用的key,可以用它来指定,默认是使用方法参数的值,我们可以编写SpELl指定
举例:指定key为getShare[1]、getShare[2]…
表达式写为key="#root.methodName+’[’+#id+’]’"
名字 位置 描述 示例 methodName root object 当前被调用的方法名 #root.methodName method root object 当前被调用的方法 #root.method.name
| target | root object | 当前被调用的目标对象 | #root.target |
| targetClass | root object | 当前被调用的目标对象类 | #root.targetClass |
| args | root object | 当前被调用方法的参数列表 | #root.args[0] |
| caches | root object | 当前方法调用使用的缓存列表(如@Cacheable(value{“cache1”, “cache2”})),则有两个cache | #root.caches[0].name |
| argument name | evaluation context | 方法参数的名字,可以直接#参数名,也可以使用#p0或#a0的形式,0代表参数的索引 | #ban、#a[0]、#p0如果是类可以#Share.id |
| result | evaluation context | 方法执行后的返回值(仅当方法执行后的判断有效,如"unless","cache put"的表达式,"cache evict"的表达式,beforeInvocation=false),注意@Cacheable注解的方法key不能使用#result得到返回值,因为方法需要在运行前就生成一个key | #result |-
keyGenerator:key的生成器,可以自己指定key的生成器的组件id,默认的key就是用一个keyGenerator生成的,key和keyGenerator二选一使用
@Configuration public class MyCacheConfig { @Bean("myKeyGenerator") public KeyGenerator keyGenerator(){ return new KeyGenerator(){ @Override public Object generate(Object o, Method method, Object... objects) { return method.getName()+"["+ Arrays.asList(objects).toString()+"]"; } }; } }
-
cacheManager:指定缓存管理器,如redis,或者cacheResolver指定缓存解析器,二选一
-
condition:指定符合条件的情况下才缓存
举例:condition="#a0>1 and …" 第一个参数的值大于一才缓存
-
unless:否定缓存,当unless指定的条件为true,方法的返回值就不会被缓存,可以获取到结果进行判断
-
sync:是否使用异步模式
-
-
缓存的原理
自动配置类:CacheAutoConfiguration
//在CacheAutoConfiguration里有这个类的selectImports方法可以得到所有的配置类,Spring Boot自动配置缓存的时候引入了很多的配置类 static class CacheConfigurationImportSelector implements ImportSelector { CacheConfigurationImportSelector() { } public String[] selectImports(AnnotationMetadata importingClassMetadata) { CacheType[] types = CacheType.values(); String[] imports = new String[types.length]; for(int i = 0; i < types.length; ++i) { imports[i] = CacheConfigurations.getConfigurationClass(types[i]); } return imports; } }
默认生效的配置有SimpleCacheConfiguration
SimpleCacheConfiguration给缓存注册了一个cacheManager
ConcurrentMapCacheManager的作用
@Bean //SimpleCacheConfiguration给缓存注册了一个cacheManager:ConcurrentMapCacheManager,可以获取和创建ConcurrentMapCacheManager类型的缓存组件,将数据存取在ConcurrentMap中 ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers) { ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager(); List<String> cacheNames = cacheProperties.getCacheNames(); if (!cacheNames.isEmpty()) { cacheManager.setCacheNames(cacheNames); } return (ConcurrentMapCacheManager)cacheManagerCustomizers.customize(cacheManager); }
运行的流程@Cacheable
-
方法运行前,先去检查缓存Cache(缓存组件),按照cacheNames指定的名字获取(CacheManager先获取相应的缓存),第一次获取缓存如果没有会自动创建
-
去Cache中查找缓存的内容,使用一个key,默认是方法的参数,key是按照某种策略生成的,策略使用keyGenerator生成,默认使用SimpleKeyGenerator生成key
SimpleKeyGenerator生成key的策略:
如果没有参数key=new SimpleKey()
有一个参数key=参数的值
有多个参数key=new SimpleKey(params)
-
没有查到缓存就调用目标方法
-
将目标方法返回的结果放进缓存中
@CacheEvict
-
key:指定要清除的缓存
-
allEntries=true:指定清除这个缓存中的所有数据
-
beforeInvocation=false:缓存的清除是否在方法之前执行
默认是在方法之后执行,例如方法中出现异常就不会清除
@Caching
//定义复杂的缓存规则 @Caching( cacheable = { @Cacheable(cacheNames = "share", key="#result.id") }, put = { @CachePut(cacheNames = "share", key="#result.tscode") } )
@CacheConfig
//定义全局的cacheconfig @CacheConfig(cacheNames = "share")
-
4、搭建Redis环境和整合Redis
百度Redis的安装,十分简单
整合Redis
@Autowired
RedisTemplate redisTemplate; //操作k-v字符串的
@Autowired
StringRedisTemplate stringRedisTemplate; //操作k-v对象的
@Autowired
RedisTemplate<Object, Object> myredisTemplate;
@Test
public void testRedis(){
stringRedisTemplate.opsForValue(); //操作String字符串
stringRedisTemplate.opsForList(); //操作list列表
stringRedisTemplate.opsForSet(); //操作set集合
stringRedisTemplate.opsForHash(); //操作Hash散列
stringRedisTemplate.opsForZSet(); //操作ZSet有序集合
//默认保存对象,会使用JDK的序列化机制,序列化后的数据保存在Redis中
//如果我们想用json格式保存数据,可以自己配置相应的Config,如下面的MyRedisConfig类
redisTemplate.opsForValue(); //与上述类似,不过可以存入对象
myredisTemplate.opsForValue().set("ceshi", shareMapper.getShareById(1));
System.out.println(myredisTemplate.opsForValue().get("ceshi"));
}
自己的RedisConfig
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Object> myredisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
template.setDefaultSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
return template;
}
}
注意如果在Redis命令行查询,先通过keys *
命令查看所有key,可以看到Redis中的key为"\"ceshi\""
,所以直接get ceshi
是无法查到的