目录
1. Spring缓存抽象简单介绍
Spring框架从3.1定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口,提供对缓存功能的声明。缓存的作用就是将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法。
缓存的自动配置类是:CacheAutoConfiguration
除此之外还有其他的配置类:
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration //【默认】
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
默认是使用SimpleCacheConfiguration配置类。我们可以打开debug,然后运行项目,在控制台就会出现那些使用了配置类:
CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字;
以下是一些重要概念和常用注解:
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存。 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在调用方法之前之后都能判断 |
allEntries | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 |
beforeInvocation | 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 |
unless | 用于否决缓存的,不像condition,该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓 |
Cache SpEL 语法
名字 | 位置 | 描述 | 示例 |
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 |
2. Spring缓存使用
2.1 @Cacheable的使用
先看一下@Cacheable的一些属性:
public @interface Cacheable {
@AliasFor("cacheNames")
String[] value() default {}; //缓存的名称
@AliasFor("value")
String[] cacheNames() default {}; //缓存的名称
String key() default ""; //通过key去查找查询的值
String keyGenerator() default ""; //指定key的生成策略,默认是参数
String cacheManager() default ""; //指定缓存管理器
String cacheResolver() default "";
String condition() default ""; //指定符合条件的情况下才缓存
String unless() default ""; //否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存
boolean sync() default false; //异步
}
1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
key默认使用SimpleKeyGenerator生成key;
SimpleKeyGenerator生成key的默认策略;
如果没有参数;key=new SimpleKey();
如果有一个参数:key=参数的值
如果有多个参数:key=new SimpleKey(params);
3、没有查到缓存就调用目标方法;
4、将目标方法返回的结果,放进缓存中
SimpleKeyGenerator生成key的策略:
下面举个例子(使用前面的项目):
先要在项目中开启缓存:在主类前面加上@EnableCaching注解
添加一个controller(通过id查询用户的方法):
@ResponseBody
@GetMapping("/getuserid")
public User getUserById(int id){
return helloService.getUserById(id);
}
补充service层:
//key默认为参数的值
@Cacheable(value = "user",key = "#id")
public User getUserById(int id) {
List<User> uList = helloMapper.getUserById(id);
if(uList.size()>0) {
return uList.get(0);
}else{
User u = new User();
u.setMessage("该用户不存在");
return u;
}
}
补充dao层:
//直接使用注解,也可以使用mapper.xml映射文件
@Select("select * from user where id=#{id}")
List<User> getUserById(int id);
为了方便我们看见是否调用了方法,我们可以在application.properties中添加一句来打印sql:
接下来运行项目,第一次访问该方法的时候,控制台打印结果如下:访问:http://localhost:8081/getuserid?id=1
刷新一下,控制台没有变化,说明使用了缓存,查询结果也在缓存里面了。
接下来,我们使用condition指定一下缓存条件:
修改一下service层的代码(id为的2的用户不缓存):
@Cacheable(value = "user",key = "#id",condition = "#id==1") //只缓存id=1的用户
public User getUserById(int id) {
List<User> uList = helloMapper.getUserById(id);
if(uList.size()>0) {
return uList.get(0);
}else{
User u = new User();
u.setMessage("该用户不存在");
return u;
}
}
修改后重新运行项目:访问(数据库添加一条数据):http://localhost:8081/getuserid?id=2
控制台打印结果:
刷新一次页面再开控制台:发现依然执行了sql,因为我们只缓存了id为1 的用户信息。
unless语法和condition相似,但是是否定判断,比如unless = "#id==2":如果参数的值是2,结果不缓存。
keyGenerator和key指定一个就可以了,后面再讲keyGenerator,这里就不做赘述了。
2.2 @CachePut的使用
依然先看看@CachePut的一些属性:
public @interface CachePut {
@AliasFor("cacheNames")
String[] value() default {}; //缓存名称
@AliasFor("value")
String[] cacheNames() default {}; //缓存名称
String key() default ""; //通过key获取缓存类容
String keyGenerator() default ""; //指定key的生成策略
String cacheManager() default "";
String cacheResolver() default "";
String condition() default ""; //当满足条件时存入缓存
String unless() default ""; //当满足条件的时候不缓存
}
这个注解主要用在更新数据的方法上面,举个例子,我们先查询了某个员工,存入了缓存当中,随后,我们需要修改这个员工的某些数据,这时候我们如果不更新缓存,那么我们再访问查询接口的时候,由于我们的结果已经存入了缓存中,不会再调用方法进行sql查询,所以我们查出来的数据依然是修改之前的。
这个时候,我们只需要在更新数据的方法上面添加@CachePut,就能够更新缓存,也能够修改数据库里面的数据。具体操作如下:
添加一个修改方法(controller层、service层、dao层):
controller层:
@ResponseBody
@GetMapping("/update")
public User update(User user){
return helloService.update(user);
}
service层:
@CachePut(value = "user",key = "#result.id") //result.id表示返回结果的id,也可以使用user.id
public User update(User user) {
helloMapper.update(user);
return user;
}
dao层:
@Update("UPDATE user SET username=#{username},password=#{password} WHERE id=#{id}")
int update(User user);
运行项目,我们先查询1号用户,因为前面我们设置了只缓存1号用户,为了方便测试,你可以把前面@Cacheable注解的condition = "#id==1"去掉(后面的测试我将这个约束都去掉了)。此时,username为admin1,密码为123456
访问:http://localhost:8081/update?id=1&username=admin&password=1111(修改username为admin,password为1111)
控制台打印结果如下:
再次访问:http://localhost:8081/getuserid?id=1
查看控制台,没有sql打印,但是查询的数据也是修改过后的数据,说明缓存中的数据也修改了。
至于condition、unless的使用和上面的@Cacheable中的一样。
(注意:要实现更新缓存,我们修改方法和查询方法的返回结果类型要保持一致,否则当我们修改后查询的时候,就会出现类型转换异常)
2.3 @CacheEvict的使用
这个注解主要用于清除缓存,比如我们删除某一条数据,这个时候也需要将缓存中的这条数据删除,否则就会出现数据库的数据删除成功,但是用户还是能够查询到被删除的数据。
@CacheEvict的一些属性:
public @interface CacheEvict {
@AliasFor("cacheNames")
String[] value() default {}; //缓存名称
@AliasFor("value")
String[] cacheNames() default {}; //缓存名称
String key() default ""; //通过key删除缓存结果
String keyGenerator() default ""; //key的生成规则
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
boolean allEntries() default false; //清除所有缓存
boolean beforeInvocation() default false; //在方法执行之前删除缓存,默认是方法执行之后删除缓存
}
还是举个例子吧:写一个删除方法(根据id删除用户信息)
controller层:
@GetMapping("/dele")
@ResponseBody
public String deleuser(int id){
int i = helloService.deleUserById(id);
if(i>0){
return "删除成功!";
}else{
return "删除失败!";
}
}
service层:
@CacheEvict(value = "user",key = "#id")
public int deleUserById(int id){
return helloMapper.deleUserById(id);
}
dao层:
@Delete("delete from user where id=#{id}")
int deleUserById(int id);
数据库里面多来几条数据:
运行项目,先来查询几个用户信息:
然后删除2号用户:
数据库中的数据:
在访问:http://localhost:8081/getuserid?id=2
缓存和数据库里面的数据都删除了。如果加上allEntries="true"的话就是删除所有缓存。
beforeInvocation="true"的话就是设置在方法执行之前清除缓存,不管方法执行是否出现异常,都清除缓存。默认是方法执行之后执行,如果方法执行过程中出现了异常,则不清除缓存。
2.4 自定义keyGenerator
keyGenerator是key的生成策略,默认使用SimpleKeyGenerator生成key。前面也说了SimpleKeyGenerator的生成策略:
那么如何自定义我们自己的key生成策略呢?
只需要编写一个配置类,类容如下:
@Configuration
public class myKeyGenerator implements KeyGenerator{
@Override
public Object generate(Object o, Method method, Object... objects) {
return "key"+ Arrays.asList(objects).toString();//指定keyd生成策略
}
}
为了看看我们自己写的keyGenerator是否有效,我们在以下地方打上断点,以debug形式运行:
这几个地方打上断点,以debug的方式启动:访问:http://localhost:8081/getuserid?id=1
一步步放行,知道出现以下地方,发现key就是以我们自定义的方式生成了:
也就说明我们之定义的keyGenerator起作用了。除此之外,我们也可以直接指定key的值来达到一样的效果:
本节类容到此结束
本人联系方式2329095893,欢迎各位进行学习讨论
欢迎关注熊熊出没ING公众号,不定时跟新Java、python、信息安全等相关知识哦。