一、前言
从Spring3.1开始,Spring引入了对Cache的支持。其使用方法和原理都类似于Spring对事务管理的支持。Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用Spring Cache的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。
SpringBoot中,CacheManager管理多个缓存Cache组件,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件都有自己的名字。
SpringBoot缓存:加载缓存自动配置类–>缓存配置类—>默认生效配置类SimpleCacheConfiguration–>给容器中注册一个CacheManager:ConcurrentMapCacheManager可以获取和创建ConcurrentMap类型的缓存组件:作用是将数据保存在ConcurrentMap中。
从上面我们大概可以了解到,SpringBoot对缓存的管理,以及缓存的形式,最主要的就是:·我们的缓存数据以key:value形式交给缓存管理器CacheManager,然后保存在一个叫做ConcurrentMap的Map中。
下面我们就从实例中来看看几个缓存注解的使用:
二、@Cacheable用法
@Cacheable注解可以作用于方法上或类上,作用于方法上表示该方法的返回值会被缓存起来,该方法具有缓存功能,而作用于类上表示该类中的所有方法都具有缓存功能。
下面我们以作用于方法上来进行实例分析:
@Cacheable(cacheNames = {"emp","temp"} ,key="#id",condition = "#id>0",unless = "#result==null")
public Employee findEmployeeById(Integer id){
System.out.println("查询编号为 "+id+" 的员工!");
return employeeMapper.findEmployeeById(id);
}
@Cacheable几个常用属性:
- cacheNames/value
缓存的名字,我们将我们的缓存数据交给缓存管理器CacheManager管理,因此我们需要指定我们缓存的名字,也就是放在哪个缓存中,这样Springboot在存取缓存中的数据时候才知道要在哪个缓存中去找。 - key
这是一个非常重要的属性,表示我们缓存数据的键,默认使用参数名作为缓存的键,值就是我们方法的返回结果。
当然,key的写法还支持SPEL表达式,这里的表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”
、“#p参数index”
、"#a参数index
"。“参数index” 表示参数列表中第几个参数。下面是几个使用参数作为key的示例
//使用#参数名方式指定
@Cacheable(value="users", key="#id")
public User find(Integer id) {
return null;
}
//使用#p参数index方式,0就表示参数列表中的第一次参数
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
return null;
}
//参数是对象的时候,我们可以使用#对象.属性方法指定
@Cacheable(value="users", key="#user.id")
public User find(User user) {
return null;
}
//即使参数是对象,我们也能用a下标或者p下标方式获取到对应参数中对应的对象
@Cacheable(value="users", key="#a0.id")
public User find(User user) {
return null;
}
当然,我们还可以通过编写配置类方式自定义配置key的生成策略。注意包不要导错哟。
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
import java.util.Arrays;
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};
}
}
在@Cacheable中使用keyGenerator
属性来进行使用我们的配置key生成策略。
@Cacheable(value="emp", keyGenerator = "myKeyGenerator")
key属性和keyGenerator属性我们只能二选一。
最后,我们的Spring还提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。我们来看看文档中这部分内容:
属性名称 | 描述 | 示例 |
---|---|---|
methodName | 当前方法名 | #root.methodName |
method | 当前方法 | #root.method.name |
target | 当前被调用的对象 | #root.target |
targetClass | 当前被调用的对象的class | #root.targetClass |
args | 当前方法参数组成的数组 | #root.args[0] |
caches | 当前被调用的方法使用的Cache | #root.caches[0].name |
@Cacheable(value="emp", key = "#root.args[0]")
public User getUserById(Integer id){
//根据id查询用户信息
User user=userMapper.getUserById(id);
return user;
}
注:当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。
- condition
有些时候我们并不希望缓存一个方法所有的返回结果。我只需要满足条件的才进行缓存。通过condition属性可以实现这一功能。condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。
@Cacheable(cacheNames = {"emp","temp"} ,key="#id",condition = "#id>0")
public Employee findEmployeeById(Integer id){
System.out.println("查询编号为 "+id+" 的员工!");
return employeeMapper.findEmployeeById(id);
}
在上面代码中,我们使用condition属性指定id大于0的结果才进行缓存。
- unless
否定缓存,当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断。 - sync
是否指定异步模式。
总结:@Cacheable标注的方法执行之前,先检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,如果没有就运行方法并将结果放入缓存。
三、@CachePut用法
在支持Spring Cache的环境下,对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是,使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中
。
@CachePut(value="emp",key = "#result.id")
public Employee updateEmployee(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmployee(employee);
return employee;
}
使用result表示方法的返回结果,这是为了同步查询功能。
也可以使用参数列表:key="#employee.id"
进行同步更新缓存功能。
解释如下:
1、查询编号为1的员工信息,放入缓存emp中。
2、更新编号为1的员工信息,此时的key如果和查询时使用的key不一样,那么,我们更新该员工之后缓存的员工信息的key值和查询缓存中的key不一样,那么我们相当于是新添加了一组缓存数据。当再次执行查询方法时候将不能得到更新之后的员工信息。
强调:@Cacheable的key是不能够使用#result.id,其实是因为第一次方法没运行之前需要先去检查缓存得到这个key,而此时缓存中并没有这个key。
@Cacheput注解一般使用在保存,更新方法中。
四、@CacheEvict注解
@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。下面看一下示例:
/**
* 删除员工信息的时候同时删除相应的缓存
* key指定要清除的数据,allEntries表示是否删除所有缓存数据
* beforeInvocation=true:当我们指定该属性值为true时,会在调用该方法之前清除缓存中的指定元素。false表示在方法执行之后执行。默认为false
* @param id
*/
@CacheEvict(value = "emp",key = "#id"/*,allEntries = true,beforeInvocation = false*/)
public void deleteEmployee(Integer id){
System.out.println("删除编号为"+id+"的员工");
employeeMapper.deleteEmployee(id);
}
五、@Caching定义复杂注解
@Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。
示例:
@Caching(
cacheable = {
@Cacheable(value = "emp",key = "#lastName")
},
put = {
@CachePut(value = "emp",key = "#result.id"),
@CachePut(value = "emp",key = "#result.email")
},
evict = {
@CacheEvict(cacheNames = "emp",allEntries = true)
}
)
public Employee findByName(String lastName){
return employeeMapper.findByLastName(lastName);
}
注:@Cacheput注解一定会执行方法,也就是说第一次按照lastName查询之后,虽然缓存中存有该数据,但是查询数据库的方法一定会执行。也就是说,当我们通过名字查询数据库的时候,即使缓存中有该数据,但还是会进行数据库的查询,只因有@Cacheput存在。
六、 @CacheConfig注解
@CacheConfig用在类上,将公共缓存内容写在该注解中,方法中这些属性即可不用再写了。
@CacheConfig(value="emp")
public class EmployeeService{
@Cacheable(key="#user.id")
public User find(User user) {
return null;
}
@Cacheable(key = "#root.args[0]")
public User getUserById(Integer id){
//根据id查询用户信息
User user=userMapper.getUserById(id);
return user;
}
}
Cache执行流程:
- 1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定名字获取;
- (Cachemanager先获取相应的缓存),第一次获取缓存如果没有缓存组件Cache会自动创建。
- 2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数 key是按照某种策略生成的:默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key,如果没有参数:key=new SimpleKey(); 如果有一个参数:key=参数的值,如果有多个参数:key=new SimpleKey(params)。
- 3、没有查找到缓存就调用目标方法。
- 4、将目标方法返回的结果放进缓存中。