前言
缓存是每一个系统都应该考虑的一个功能系统的访问以及提升系统的性能。
JSR107
JSR是Java Specification Requests 的缩写 ,Java规范请求,故名思议提交Java规范,大家一同遵守这个规范的话,会让大家‘沟通’起来更加轻松, JSR-107呢就是关于如何使用缓存的规范。
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中的条目有一个定义的有效期。一旦超过这个时间,条目为过期 的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期
Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache 和org.springframework.cache.CacheManager接口来统一不同的缓存技术; 并支持使用JCache(JSR-107)注解简化我们开发;
-
Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
-
Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
-
每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否 已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法 并缓存结果后返回给用户。下次调用直接从缓存中获取。
-
使用Spring缓存抽象时我们需要关注以下两点;
1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据
概念&缓存注解
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
---|---|
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存。 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
@Cacheable/@CachePut/@CacheEvict 主要的参数
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=”#userName.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”) |
名字 | 位置 | 描述 | 示例 |
---|---|---|---|
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代表参数的索引; | #iban 、 #a0 、 #p0 |
result | evaluation context | 方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式beforeInvocation=false) | #result |
Spring Boot缓存工作原理及@Cacheable运行流程
根据上面的示例分析一下Spring Boot 缓存的步骤及工作原理
1、如果需要分析自动配置的原理就需要分析自动配置类:CacheAutoConfiguration:
2、这个自动配置中导入了一个类CacheConfigurationImportSelector
,这个类会引入一些缓存配置类。
3、在全局配置文件中设置属性debug=true,这样就会打印所有的配置报告。
4、通过打印日志可以看出SimpleCacheConfiguration
配置类默认生效。这个配置类给容器中注册了一个CacheManager
。
5、缓存方法运行之前,先按照cacheNames查询缓存组件,第一次获取缓存如果没有缓存创建一个。
总结 @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去查询缓存, 如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
Spring Boot 中Cache缓存使用
-
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
-
@EnableCaching开启缓存
@MapperScan("com.sggg.cache.mapper") @SpringBootApplication @EnableCaching //开启缓存注解扫描 public class ComCacheApplication { public static void main(String[] args) { SpringApplication.run(ComCacheApplication.class, args); } }
-
@Cacheable缓存注解的使用 (一般情况下标注在service业务层方法上)
执行流程:先执行@Cacheable注解中的getCache(String name)方法,根据name判断ConcurrentMap中是否有此缓存,如果没有缓存那么创建缓存并保存数据,另外service层的方法也会执行。如果有缓存不再创建缓存,另外service层的方法也不会执行。
总结:先执行@Cacheable然后再执行service层的方法
@Cacheable注解属性:
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Cacheable { @AliasFor("cacheNames") String[] value() default {}; @AliasFor("value") String[] cacheNames() default {}; String key() default ""; String keyGenerator() default ""; String cacheManager() default ""; String cacheResolver() default ""; String condition() default ""; String unless() default ""; boolean sync() default false; }
详解:
- cacheNames/value:指定缓存组件的名字
- key:缓存数据使用的key,可以用它来指定。默认使用方法参数的值,一般不需要指定
- keyGenerator:作用和key一样,二选一
- cacheManager和cacheResolver作用相同:指定缓存管理器,二选一
- condition:指定符合条件才缓存,比如:condition="#id>3";也就是说传入的参数id>3才缓存数据
- unless:否定缓存,当unless为true时不缓存,可以获取方法结果进行判断
- sync:是否使用异步模式
service原理&代码
-
第一次查询数据库打印service类方法日志,并把数据保存到Cahce中
-
第二次传入相同参数不再执行service类方法,不会打印日志,查询的数据直接从缓存中获取
@Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; @Cacheable(cacheNames= "emp") public Employee getEmpById(Integer id){ System.out.println("学号"+id+"进入"); Employee employee=employeeMapper.getEmpById(id); return employee; } }
4. CachePut注解
接着 Cacheable注解 讲解一下其他的注解。其中 CachePut
,CacheEvict
属性大部分和Cacheable
中的属性差不多。
@CachePut 作用:既 调用方法,又更新缓存数据;同步更新缓存。说简单点就是 修改了数据库的某个数据,同时更新缓存;
写个实例,帮助我们理解
Control
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee Employee(@PathVariable("id")Integer id){
Employee employee = employeeService.getEmpById(id);
return employee;
}
@GetMapping("/emp")
public Employee update(Employee employee){
Employee emp = employeeService.updateEmp(employee);
return emp;
}
}
Service
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
@Cacheable(cacheNames= "emp")
public Employee getEmpById(Integer id){
System.out.println("学号"+id+"进入");
Employee employee=employeeMapper.getEmpById(id);
return employee;
}
@CachePut(value = "emp")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
接口省略,和平常写的都一样
看一下效果
上面的实验效果明显不对,数据库更新过后但是缓存并没有跟着更新,我们怎样他们都更新那。
* @CachePut:既调用方法,又更新缓存数据;同步更新缓存
* 修改了数据库的某个数据,同时更新缓存;
* 运行时机:
* 1、先调用目标方法
* 2、将目标方法的结果缓存起来
*
* 测试步骤:
* 1、查询1号员工;查到的结果会放在缓存中;
* key:1 value:lastName:张三
* 2、以后查询还是之前的结果
* 3、更新1号员工;【lastName:zhangsan;gender:0】
* 将方法的返回值也放进缓存了;
* key:传入的employee对象 值:返回的employee对象;
* 4、查询1号员工?
* 应该是更新后的员工;
* key = "#employee.id":使用传入的参数的员工id;
* key = "#result.id":使用返回后的id
* @Cacheable的key是不能用#result
* 为什么是没更新前的?【1号员工没有在缓存中更新】
*
*/
@CachePut(value = "emp",key = "#result.id")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
Service层改为上面的代码就可以实现
CacheEvict
@CacheEvict
的作用:清除缓存中的指定数据或清除缓存中所有数据。简单说就是 缓存清除
@CacheEvict 中的属性和 Cacheable 中的属性有两个不同,下面看一下:
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
//上面的属性和前面的注解一样,下面的这两个有所不同
boolean allEntries() default false;
boolean beforeInvocation() default false;
@Service
public class EmployeeService{
@Autowired
EmployeeMapper employeeMapper;
/**
* @CacheEvict:清除缓存
* 1.key:指定要清除缓存中的某条数据
* 2.allEntries=true:删除缓存中的所有数据
* beforeInvocation=false:默认是在方法之后执行清除缓存
* 3.beforeInvocation=true:现在是在方法执行之前执行清除缓存,
* 作用是:只清除缓存、不删除数据库数据
*/
//@CacheEvict(cacheNames = "person",key = "#id") 定要清除缓存中的某条数据
@CacheEvict(value="emp",allEntries = true) //allEntries=true:删除缓存中的所有数据
public void deleteEmp(Integer id){
/**
* beforeInvocation=true
* 使用在方法之前执行的好处:
* 1.如果方法出现异常,缓存依旧会被删除
*/
//int a=1/0;
}
}
@CacheConfig&@Caching
@Caching
@Caching是@Cacheable、@CachePut、@CacheEvict注解的组合,看一下源码
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
- 作用:用于复杂的缓存操作
@CacheConfig
这个注解的的主要作用就是全局配置缓存,比如配置缓存的名字(cacheNames),只需要在类上配置一次,下面的方法就默认以全局配置为主,不需要二次配置,节省了部分代码。