Spring Cache使用


因为不同的技术需要不同的CacheManager,比如我们的spring-boot-starter-data-redis就默认使用的是redis技术作为缓存。
我们这里先使用最基本的CacheManage,那么它用导什么坐标呢。
其实最基本的CacheManger就在我们的spring-boot-starter-web下的webmvc->context中

 
 
 
然后在controller中出入CacheManger,

ctrl+b跟进后发现CacheManger是个接口,默认提供了几种缓存实现。

现在它使用的其实就是ConcurrentMapCacheManager这种实现
底层实现其实是基于这个ConcurrentMap这种实现来缓存数据

 
 
 
鼠标停留到CacheManage发现它就是context包下的,也验证了前面说的。

你想要使用这个缓存的话记得在启动类上加上@EnableCaching注解

 
然后再来看一下它的常用注解。

CachePut注解
如果缓存需要更新,且不干扰方法的执行,可以使用注解@CachePut。@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
 /**
     * @description:  CachePut注解:将方法返回值存入缓存
     * value:缓存的名称,每个缓存名称下面可以有多个key
     * key:缓存的key
     * @param user
     * @return: com.itheima.entity.User
     * @author: MR.孙
     * @date: 2022/7/2 20:10
    */
    @CachePut(value = "UserCache",key = "#user.id")
    @PostMapping
    public User save(User user){
        log.info("新增之前的id->:{}",user.getId());
        userService.save(user);
        log.info("新增之后的id->:{}",user.getId());
        return user;
    }
value代表缓存的名称,假如我要把user信息存到同一个缓存,我可以其一个value,多个key来表示不同的缓存。
#user.id:user代表返回值对象,id是user的属性

其他的使用。看下源码注释文档把
你可以使用#result来引用返回值的对象

调试后发现这个CacheManage果然还是用的cachemap实现的,第一次缓存里面没有东西。

那么怎么看它缓存成功了没了,再发一次请求,抓到这个缓存管理器对象,发现里面的size为1,说明上一次已经缓存成功了。

注意啊,这个缓存是基于内存的,如果我重新编译运行一下程序,缓存是没有的了。
CacheEvict注解
spring cache不仅支持将数据缓存,还支持将缓存数据删除。此过程经常用于从缓存中清除过期或未使用的数据。
@CacheEvict要求指定一个或多个缓存,使之都受影响。此外,还提供了一个额外的参数allEntries 。表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素。
 /**
     * @description:  CacheEvict:清理指定缓存
     * @param id
     * @return: void
     * @author: MR.孙
     * @date: 2022/7/2 22:51
    */
    @CacheEvict(value = "UserCache",key = "#id")
//    @CacheEvict(value = "UserCache",key = "#p0")
//    @CacheEvict(value = "UserCache",key = "#root.args[0]")
    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id){
        userService.removeById(id);
    }
这里注解要想用方法的参数有三种方式
还是用stl表达式,直接#加参数名,名称要一致,这种是最简单的。
另外这两呢0代表的是第一个参数,用法是固定的。

清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
另外我们在做更新操作的时候,也是需要进行清除缓存的操作的,因为和缓存中的数据和数据库中的数据不一致了。
 /**
     * @description:  CacheEvict:清理指定缓存
     * @param user
     * @return: com.itheima.entity.User
     * @author: MR.孙
     * @date: 2022/7/2 23:17
    */
//    @CacheEvict(value = "UserCache",key = "#user.id")
//    @CacheEvict(value = "UserCache",key = "#p0.id")
//    @CacheEvict(value = "UserCache",key = "#root.args[0].id")
    @CacheEvict(value = "UserCache",key = "#result.id")
    @PutMapping
    public User update(User user){
        userService.updateById(user);
        return user;
    }
Cacheable注解
@Cacheable是用来声明方法是可缓存的。将结果存储到缓存中以便后续使用相同参数调用时不需执行实际的方法。直接从缓存中取值。最简单的格式需要制定缓存名称。
 @Cacheable(value = "UserCache",key = "#id")
    @GetMapping("/{id}")
    public User getById(@PathVariable Long id){
        User user = userService.getById(id);
        return user;
    }
现在通过postman发送一个post请求,把name为d的user信息存到缓存里。

然后用get请求根据id去查询user信息,第一次缓存中有数据是直接返回的,如果缓存中没数据就结果存储到缓存中,下次调用直接返回。
这里先测缓存中有的
然后发现它是直接存缓存中取的,没有经过debug模式。

再来测一下不存在的id,它会先查数据库然后把结果放到缓存中,以后也是直接从缓存中取。
传个name=123的发现进入debug模式,一直卡在发送请求的地方。


后来访问这个url就是直接就返回了,没有经过debug模式。

然后在新增一个,看看cachemanage中是否有它的缓存。
可以看到已经有缓存了,直接是从缓存中取的。

但是由于查不到结果所以它保存的是null类型的。
缓存穿透
这就要引入一个概念了,叫做缓存穿透


缓存击穿


接着cacheable注解往下说,这个刚刚我们的null类型也加入了缓存中,那如果我们要不为null的类型加入缓存中,用cacheable注解怎么实现呢?
condition就是满足条件是缓存,user不为null时缓存
/**Cacheable:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据,
     * 若没有数据,调用方法并将方法返回值缓存
     * condition:条件,满足条件时才缓存数据
     * unless:满足条件时不缓存数据
     * @description:
     * @param id
     * @return: com.itheima.entity.User
     * @author: MR.孙
     * @date: 2022/7/3 9:04
    */
    @Cacheable(value = "UserCache",key = "#id",condition = "#result!=null")
    @GetMapping("/{id}")
    public User getById(@PathVariable Long id){
        User user = userService.getById(id);
        return user;
    }
因为不满足condition条件,所以无论发送多少次请求都会进入到debug,进入数据库查询,这样就会造成缓存穿透了。

还有就是key的值要保证唯一,可以通过动态的拼接查询条件
 @Cacheable(value = "UserCache",key = "#user.id+'_'+#user.name")
    @GetMapping("/list")
    public List<User> list(User user){
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(user.getId() != null,User::getId,user.getId());
        queryWrapper.eq(user.getName() != null,User::getName,user.getName());
        List<User> list = userService.list(queryWrapper);
        return list;
    }
这个底层的cachemanage是基于内存的,重新编译就没了,下篇说一下换成reids的缓存。

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号