缓存,对一个成熟的系统来说是必不可少的,对于一些热点数据,一般都会放在缓存里,这样每次访问的时候,就不需要从数据库中获取了。
JSR107规范中定义了缓存相关的接口,使用JSR107整合系统的难度比较大,所以很少用,一般使用的是SpringBoot的缓存抽象,SpringBoot缓存抽象中的底层概念和JSR107规范中的是通用的。
cacheManager就类似于数据库连接池,cahce类似于不同的数据源。
Spring从3.1开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口来统一不同的缓存技术, 并支持使用JCache(JSR-107)注解简化我们开发
xxxCache
的实现;如RedisCache
,EhCacheCache
, ConcurrentMapCache
等;每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
使用Spring缓存抽象时我们需要关注以下两点
Cache
、CacheManager
用于管理缓存,对缓存中的数据进行增删改查操作。@Cacheable
、@CacheEvict
、@CachePut
可以简化开发
@Cacheable
注解,每次查询,就会将结果放到缓存中@CacheEvict
注解,这样,每次删除数据库中数据的同时,也会将缓存中的数据清除,@CachePut
,每次更新数据中中某条数据时,也会更新缓存中的某条数据。基本使用步骤
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
首先看缓存的自动配置类CacheAutoConfiguration
,这个类给容器中导入了CacheConfigurationImportSelector
类
接着看CacheConfigurationImportSelector
类,此类的selectImports()
方法添加了许多缓存的配置类,每个配置类上都有条件,在什么情况下生效,其中SimpleCacheConfiguration
默认生效,可以在配置文件中加上debug=true
,查看生效的配置类
看SimpleCacheConfiguration
类,这个类注册了一个ConcurrentMapCacheManager
ConcurrentMapCacheManager
类实现了CacheManager
接口,重写了getCache(String var1)
方法,通过getCache
方法获得Cache,如果没有,会创建一个ConcurrentMapCache
ConcurrentMapCache
使用ConcurrentMap
以k-v的方式存储缓存,并且ConcurrentMapCache
中有很多操作缓存的方法
以@Cacheable
为例(在getCache
、ConcurrentMapCache
类的lookup
、put
,以及@Cacheable
标注的方法中打断点分析)
cacheName
指定的名字获取Cache(CacheManager
中getCaceh方法获取相应的缓存),第一次获取的时候,因为Cache为null,所以会调用createConcurrentMapCache(name)
方法创建,之后,把创建好的ConcurrentMapCache
放在cacheMap
中。keyGenerator
生成的,SimpleKeyGenerator
实现了keyGenerator
接口,所以默认使用SimpleKeyGenerator
生成key
SimpleKeyGenerator
生成key的默认策略
@Cacheable
标注的方法)总结
@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
核心
- 使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
- key使用keyGenerator生成的,默认是SimpleKeyGenerator
//@CacheConfig标注在类上,用于抽取@Cacheable的公共属性
//由于一个类中可能会使用多次@Cacheable等注解,所以各项属性可以抽取到@CacheConfig
@CacheConfig(cacheNames = "emp")
@Service
public class EmpolyeeService {
@Autowired
EmployeeMapper employeeMapper;
@Cacheable(value="emp" , key="#id")
public Employee selectById(Integer id) {
System.out.println("查询了"+ id +"员工");
Employee employee = employeeMapper.selectById(id);
return employee;
}
/*@CachePut及调用方法,有更新缓存数据
修改了数据库中的某个数据,有更新缓存
运行时机:
1.先调用目标方法
2.将目标方法的结果缓存起来
测试步骤:
1.查询1号员工,查询结果将放到缓存中,再次查询的话,就不会访问数据库
2.更新1号员工
3.再次查询1号员工,若果不指定key的话,key就是方法的参数,和查询的key,不一样
得到的就不是修改之后的数据,而是没有修改的数据。
指定key为id:
key="#emp.id"
key="#result.id",注意,@Cacheable不能使用这个,因为@Cacheable
在目标执行方法执行之前就获得key,这时候,结果为空,会报空指针异常
*/
@CachePut(value="emp" ,key="#emp.id" )
public Employee update(Employee emp) {
System.out.println("更新了" + emp);
employeeMapper.update(emp);
return emp;
}
/*@CacheEvict 清除缓存
key:指定要删除的数据
allEntries=true,删除这个缓存中的所有数据,默认是false
beforeInvocation,缓存的清除是否在方法之前执行,默认是false,是在方法
之后执行的,如果方法出错,缓存就没法清除,如果设为true,表示在方法
之前执行,无论方法是否出错,缓存都会清除
*/
@CacheEvict(value="emp")
public void deleteById(Integer id) {
System.out.println("删除了" + id);
//这里只删除缓存中的数据,不删除数据库中的
//employeeMapper.deleteById(id);
}
/*@Caching ,可以指定多个缓存规则
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
*/
@Caching(
cacheable = {@Cacheable(value="emp",key="#lastName")},
put = {@CachePut(value="emp", key="#result.id"),
@CachePut(value="emp", key="result.gender")}
)
public Employee selectByLastame(String lastName) {
Employee employee = employeeMapper.selectByLastname(lastName);
return employee;
}
}
开发中,一般使用的是缓存中间件,比如Redis、memCache、EhCache,根据前面SpringBoot的缓存原理可知,你要使用哪个缓存,就使用哪个缓存的xxxCacheConfigura类,只要导入相应的缓存类之后,就会注册相应的xxxCacheConfiguration
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring.redis.host=47.94.231.234
RedisAutoConfiguration
向容器中导入了两个类
redisTemplate
:操作的k-v都为对象StringRedisTemplate
:操作的k-v都为字符串Redis 常用的五种数据类型
String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
RedisTemplate类中的opsForValue方法操作字符串,还有opsForXxx来操作相应的数据类型,相当于获取的是一种操作对象,使用操作对象来操作相应的数据结构
操作对象
RedisCache进行缓存数据,要缓存的对象的类要实现Serializable接口,否则会报错,默认情况下是以jdk序列化数据存在redis中
一般是以json的形式将数据存储在redis中。将对象转换成json
注意,这里必须用GenericJackson2JsonRedisSerializer
进行value的序列化解析,如果使用Jackson2JsonRedisSerializer
,序列化的json没有"@class": "cn.edu.ustc.springboot.bean.Employee"
,在读取缓存时会报类型转换异常。
RedisCacheConfiguration
会生效,RedisCacheConfiguration
向容器中注册了RedisCacheManager
RedisCacheManager
创建RedisCache
来作为缓存组件RedisCache
,RedisCache
操作Redis缓存数据,也就是说,当你导入Redis的启动器,再使用注解的话,数据会保存到Redis中RedisCacheConfiguration
中设置了JDK的序列化器JdkSerializationRedisSerializer
CacheManager
,使用这种方法,缓存数据的时候,都会转换成jsong格式。@Configuration
public class RedisConfig {
//自定义cacheManager来设置序列化器为GenericJackson2JsonRedisSerializer
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))
.disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();
}
}
如有不足之处,欢迎指正,谢谢!