Spring Boot web开发(八) Spring缓存抽象

 

目录

 

1. Spring缓存抽象简单介绍

2. Spring缓存使用

2.1 @Cacheable的使用

2.2 @CachePut的使用

2.3 @CacheEvict的使用

2.4 自定义keyGenerator


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、信息安全等相关知识哦。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值