数据库的数据 是存储在 硬盘上 的,频繁访问 性能 较 低。而 缓存数据 存储在 内存 中,访问 性能 比硬盘 快 了一个数量级。如果将一些需要 频繁查询 的热数据放到 缓存 中,可以大大减轻数据库的访问压力。
[TOC]
Spring Cache
Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到 缓存方法的返回对象
的效果。
Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持主流的专业缓存例如 EHCache
集成。
声明式缓存
Spring 定义 CacheManager 和 Cache 接口用来统一不同的缓存技术。例如 JCache、 EhCache、 Hazelcast、 Guava、 Redis
等。在使用 Spring 集成 Cache 的时候,我们只需要提供实现的 CacheManager 的 Bean,然后在方法上写一行注解,声明一下这个方法的查询是要用缓存的即可。
Spring Boot默认集成CacheManager
SpringBoot 已经为我们自动配置了多个 CacheManager 的实现。
我们可以在 application.yml 中用spring.cache.type
指定缓存供应商类型(默认情况下,根据环境自动检测),如果 SpringBoot 在配置中找不到用户指定的 Cache Providers
,那么 ConcurrentMapCacheManager 会被作为 缓存管理器。
环境/版本一览:
- 开发工具:Intellij IDEA 2018.2.2
- springboot: 2.0.5.RELEASE
- jdk:1.8.0_171
- maven:3.3.9
- spring-boot-starter-cache:2.0.5.RELEASE
1、pom.xml
1 | <dependencies> |
2、entity
实体必须实现序列化
1 | package com.fatal.entity; |
3、dao
IUserDao.java
1 | package com.fatal.dao; |
UserDaoImpl.java
1 | package com.fatal.dao.impl; |
4、service
IUserService.java
1 | package com.fatal.service; |
UserServiceImpl.java
声明式很简单,在指定方法上标注上对应的注解即可
1 | package com.fatal.service.impl; |
5、Application
使用
@EnableCaching
开启 Cache 缓存
1 | package com.fatal; |
6、Test
UserServiceImplTest.java
测试缓存
1 | package com.fatal.service.impl; |
Chapter15ApplicationTests.java
测试CacheManager的默认实现
1 | package com.fatal; |
显示
测试缓存
执行测试方法 selectById ,在添加完后,添加数据被存放到缓存中,所以,后面查询的时候数据从缓存中取,所以没显示 进入【selectById】方法
测试CacheManager的默认实现
用户没有作配置的情况下,CacheManager 取得是 ConcurrentMapCacheManager,所以下方显示为同一个 实例
笔记
根据条件操作缓存
根据条件操作缓存内容并不影响数据库操作,条件表达式返回一个布尔值,true/false,当条件为true,则进行缓存操作,否则直接调用方法执行的返回结果。
- 长度:
@CachePut(value = "user", key = "#user.id",condition = "#user.username.length() < 10")
只缓存用户名长度少于10的数据 - 大小:
@Cacheable(value = "user", key = "#id",condition = "#id < 10")
只缓存ID小于10的数据 - 组合:
@Cacheable(value="user",key="#user.username.concat(#user.password)")
- 提前操作:
@CacheEvict(value="user",beforeInvocation=true)
加上beforeInvocation=true
后,不管内部是否报错,缓存都将被清除,默认情况为false
key 和 condition 的变量取自参数
注解介绍
@Cacheable (根据方法的请求参数对其结果进行缓存)
- key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合(如:
@Cacheable(value="user",key="#userName")
) - value: 缓存的名称,必须指定至少一个(如:
@Cacheable(value="user")
或者@Cacheable(value={"user1","use2"})
) - condition: 缓存的条件(针对参数),可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存(如:
@Cacheable(value = "user", key = "#id",condition = "#id < 10")
) - unless:缓存的条件(针对返回结果),可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存(如:
@Cacheable(value = "user", key = "#id", unless = "#result.getCode() != 0")
。result
指返回的对象,这里指ResultVO
。 因为错误的结果我们不可能缓存进来嘛)
@CachePut (根据方法的请求参数对其结果进行缓存,和
@Cacheable
不同的是,它每次都会触发真实方法的调用 )注:它要求标注的方法的返回值类型与name相同的标注有
@Cacheable
方法的返回值类型一致,否则报错(类型转换异常)
- key: 同上
- value: 同上
- condition: 同上
@CachEvict (根据条件对缓存进行清空)
- key: 同上
- value: 同上
- condition: 同上
allEntries: 是否清空当前
value
下的所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存(如:@CacheEvict(value = "user", allEntries = true)
)注意:默认情况下,只删除关联 key下的缓存;需要特别注意的是,当
allEntries = true 时
,不允许设置 key 值。(二选一)beforeInvocation: 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存(如:
@CacheEvict(value = "user", key = "#id", beforeInvocation = true)
)
@CacheEvict 中的 allEntries 与 beforeInvocation 的区别
在 Spring Cache 中,@CacheEvict 是清除缓存的注解。其中注解参数可以只有 value 和 key ,意思是清除在 value 值空间中的 key 值数据,此时默认在当前注解方法成功执行之后再清除。这时候就会存在一个问题,也许你的注解方法成功执行了删除操作,但是后续代码 抛出异常 导致未能清除缓存,下次查询时依旧从缓存中去读取,这时查询到的结果值是删除操作之前的值。
有一个简单的解决办法,在注解参数里面加上 beforeInvocation为 true,意思是说当执行这个方法之前执行清除缓存的操作,这样 不管这个方法执行成功与否,该缓存都将不存在。
当注解参数加上 allEntries 为 true 时,意思是说这个清除缓存是 清除 当前 value 值空间下 的所有缓存数据。
探究
为什么 CacheManager 的默认实现是 ConcurrentMapCacheManager?
官方文档说:
如果找不到任何 CacheManager 的提供者(实现),那么将会以 ConcurrentMapCacheManager (底层使用 ConcurrentHashMap)作为一个 简单的缓存管理器 从而进行默认配置。
看一下源码:
这是缓存配置类 SimpleCacheConfiguration
这里是缓存管理器 ConcurrentCacheManager 的内部实现
分析
其实所有的 Cache 缓存配置类,Spring Boot 都提供了默认 配置(就差你引入依赖就可以用了),我们引入 spring-boot-starter-data-cache
后,该依赖提供了 SimpleCacheConfiguration 需要的缓存管理器实现 ConcurrentMapCacheManager ;
而像其它缓存管理器 实现,比如 Redis 缓存管理器
RedisCacheManager 需要的依赖 spring-boot-starter-data-redis
,要求我们手动去添加;
当我们没有添加 指定管理器 需要的依赖, ConcurrentMapCacheManager 就成了 默认 的缓存管理器;当我们引入的话, SpringBoot 会扫描出来,并将其 替代 默认的缓存管理器。
(换行的话可能方便看)
具体思路可参考源码:
参考资料
SpringBoot非官方教程 | 第十三篇:springboot集成spring cache
一起来学SpringBoot | 第十篇:使用Spring Cache集成Redis
Spring缓存注解@Cacheable,@CachePut , @CacheEvict使用
总结
SpringBoot
的知识已经有前辈在我们之前探索了。比较喜欢的博主有:唐亚峰 | Battcn、方志朋的专栏、程序猿DD、纯洁的微笑。对这门技术感兴趣的可以去他们的博客逛逛。谢谢他们的分享~~
以上文章是我用来学习的Demo
,都是基于 SpringBoot2.x
版本。
源码地址: https://github.com/ynfatal/springboot2-learning/tree/master/chapter15