Spring Boot与缓存 笔记
用途:
提升查询效率(商品信息存入缓存。系统先去缓存找,没有再去数据库找,然后把找到的又放入缓存)
存储临时数据(比如验证码)
一、JSR107缓存规范
Java Caching定义了5个核心接口:
CachingProvider管理和控制多个CacheManager;
CacheManager管理和控制多个唯一命名的Cache;
Cache是一个类似Map的数据结构,存储多个key-value对;
Entry是一个存储在Cache中的key-value对;
Expiry是一个存储在Cache中的定义的有效期。
二、Spring缓存抽象
Spring对缓存进行了抽象,更便于使用
Cache:缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConCurrentMapCache等。
CacheManager:缓存管理器,管理各种缓存(Cache)组件
三个基本注解,标注在方法上,简化常见的缓存操作:
@Cacheable:设置缓存。对方法的结果进行缓存(一旦被调用过有数据,就不会再调用)
@CacheEvict:清空缓存
@CachePut:更新缓存(跟@Cacheable区别:方法总会被调用)
@EnableCaching:开启基于注解的缓存
三、缓存实操
(一) 搭建基本环境
1.导入数据库文件,创建出department和employee表
2.创建javaBean封装数据
3.整合MyBatis操作数据库
1.配置数据源信息
2.使用注解版的MyBatis
1)@MapperScan指定需要扫描的mapper接口所在的包
(二) 快速体验缓存
步骤:
1.开启基于注解的缓存@EnableCaching
2.标注缓存注解即可
@Cacheable
@CacheEvict
@CachePut
默认使用的是ConcurrentMapCacheManager==ConcurrentMapCache,将数据保存在ConcurrentMap<Object, Object>中;
开发中使用缓存中间件:redis、memcached、ehcache;
(三) 整合redis作为缓存
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
1.安装redis,使用docker;
2.引入redis的starter
3.配置redis
4.测试缓存
原理:CacheManager==Cache 缓存组件来实际给缓存中存取数据
1)引入redis的starter,容器中保存的是 RedisCacheManager;
2)RedisCacheManager 帮我们创建RedisCache来作为缓存组件,RedisCache通过操作redis缓存数据的
3)默认保存数据 k-v 都是Object,利用序列化保存;
如何保存为json:
1.引入了redis的starter,CacheManager变为 RedisCacheManager
2.默认创建的RedisCacheManager操作redis的时候使用的是 RedisTemplate<Object, Object>
3.RedisTemplate<Object, Object> 是默认使用jdk的序列化机制
4)自定义CacheManager
四、注解讲解
@CacheConfig(cacheNames = "emp") //抽取缓存的公共配置
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
/**
* 将方法的运行结果进行缓存,以后再要相同的数据,直接从缓存中获取,不再调用方法
*
* CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字
* 几个属性:
* cacheNames/value:指定缓存的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
* key:缓存数据使用的key,可以用它来指定。默认是使用方法参数的值 如:1-方法的返回值
* 编写SpEl: #id:参数id的值 也可以用 #a0 #p0 #root.args[0]
* getEmp[2] : key = "#root.methodName+'['+#id+']'"
* keyGenerator:key的生成器;可以自己指定key的生成器的组建的id
* key/keyGenerator:二选一使用
*
* cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
* condition:指定符合条件的情况下才缓存;
* 如:condition = "#id>0"
* condition = "#a0>1" 第一个参数的值大于1的时候才进行缓存
* unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存,可以获取到结果进行判断
* 如:unless = "#result == null"
* sync:是否使用异步模式
*
* 原理:
* 1.自动配置类: CacheAutoConfiguration
* 2.缓存的配置类 11个
*
* 3.哪个配置类默认生效: SimpleCacheConfiguration
* 4.给容器中注册了一个CacheManager: ConcurrentMapCacheManager
* 5.可以获取和创建ConcurrentMapCache类型的缓存组件,他的作用 将数据保存在ConcurrentMap中
*
* 运行流程:
* @Cacheable :
* 1.方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
* (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建
* 2.去Cache中查找缓存的内容,使用一个key(默认就是方法的参数)
* key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key
* SimpleKeyGenerator生成key的默认策略:
* 如果没有参数:key=new SimpleKey();
* 如果有一个参数:key=参数的值
* 如果有多个参数:key=new SimpleKey(params);
* 3.没有查到缓存就调用目标方法;
* 4.将目标方法返回的结果,放进缓存中;
*
* @Cacheable 标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询,
* 如果没有就运行方法,并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
*
* 核心:
* 1)使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
* 2)key是使用keyGenerator生成的,默认是SimpleKeyGenerator
*
* @param id
* @return
*/
@Cacheable(/*cacheNames = {"emp"},*/key = "#a0")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
/**
* @CachePut :既调用方法,又更新缓存数据;
* 修改了数据库的某个数据,同时更新缓存
* 运行时机:
* 1.先调用目标方法
* 2.将目标方法的结果缓存起来
*
* 测试步骤:
* 1.查询1号员工,查到的结果会放在缓存中
* key:1 value:lastName:张三
* 2.以后查询还是之前的结果
* 3.更新1号员工;【lastName:zhangsan;gender:0】
* 将方法的返回值也放进缓存了;
* key:传入的employee对象 值:返回的employee对象
* 4.查询1号员工
* 应该是更新后的员工,结果不是,因为key不同
* key = "#employee.id",使用传入的参数的员工id
* key = "#result.id",使用返回后的id
* @Cacheable 的key不能用#result
*
*/
@CachePut(/*value = "emp",*/key = "#result.id")
public Employee updateEmployee(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
/**
* @CacheEvict 缓存清除
* key:指定要清除的数据
* allEntries = true:指定清除这个缓存中所有的数据
* beforeInvocation = false:缓存的清除是否在方法之前执行
* 默认为false,代表是在方法执行之后执行;如果方法出现异常,缓存就不会清除
* 如果为true,代表是在方法执行之前执行;无论方法是否出现异常,缓存都会清除
*
*/
@CacheEvict(/*value="emp",*/key = "#id",beforeInvocation = false)
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
//employeeMapper.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 employeeMapper.getEmpByLastName(lastName);
}
}
五、SpringBoot2.x整合redis缓存自定义序列化的实现
我们使用redis作为缓存中间件时,当我们第一次查询数据的时候,是去数据库查询,然后查到的数据封装到实体类中,实体类会被序列化存入缓存中,当第二次查数据时,会直接去缓存中查找被序列化的数据,然后反序列化被我们获取。
我们在缓存中看到的序列化数据不直观,如果想看到类似json的数据格式,就需要自定义序列化规则。
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(ser);
return template;
}
@Bean(name = "redisCacheManager")
@Primary //将某个缓存管理器作为默认的
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(100)) //设置过期时间
.disableCachingNullValues() //禁止缓存null对象
.computePrefixWith(cacheName -> "sprigboot_cache".concat(":").concat(cacheName).concat(":") )//此处定义了cache key的前缀,避免公司不同项目之间的key名称冲突
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) //定义了key和value的序列化协议,同时hash key和hash value也被定义
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
}