一、Spring Boot之缓存
SpringBoot中,可以使用以下三种缓存方式:
- JSR-107
- Spring缓存抽象
- 整合Redis
1、JSR-107
Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。
- CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可 以在运行期访问多个CachingProvider。
- CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache 存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
- Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个 CacheManager所拥有。
- Entry是一个存储在Cache中的key-value对。
- Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期 的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-imwH6uQc-1598323780176)(IMAGES/Snipaste_2020-08-04_20-52-35.png)]](/https://i-blog.csdnimg.cn/blog_migrate/7efc9696e2866a055e2db5b17abc4dca.png#pic_center)
2、Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术; 并支持使用JCache(JSR-107)注解简化我们开发;
- Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
- Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
- 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否 已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法 并缓存结果后返回给用户。下次调用直接从缓存中获取。
- 使用Spring缓存抽象时我们需要关注以下两点;
- 确定方法需要被缓存以及他们的缓存策略
- 从缓存中读取之前缓存存储的数据
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jntviggK-1598323780182)(IMAGES/Snipaste_2020-08-04_20-55-04.png)]](/https://i-blog.csdnimg.cn/blog_migrate/8e997ee34594227a534c65444682504e.png#pic_center)
3、几个重要概念&缓存注解
| 作用 | |
|---|---|
| Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、 ConcurrentMapCache等 |
| CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
| @Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
| @CacheEvict | 清空缓存 |
| @CachePut | 保证方法被调用,又希望结果被缓存。 |
| @EnableCaching | 开启基于注解的缓存 |
| keyGenerator | 缓存数据时key生成策略 |
| serialize | 缓存数据时value序列化策略 |
@Cacheable/@CachePut/@CacheEvict 主要的参数
| 作用 | 例子 | |
|---|---|---|
| value | 缓存的名称,在 spring 配置文件中定义,必须指定 至少一个 | 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
| key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达 式编写,如果不指定,则缺省按照方法的所有参数 进行组合 | 例如: @Cacheable(value=”testcache”,key=”#userName”) |
| condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在 调用方法之前之后都能判断 | 例如: @Cacheable(value=”testcache”,condition=”#userNam e.length()>2”) |
| allEntries (@CacheEvict ) | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 | 例如: @CachEvict(value=”testcache”,allEntries=true) |
| beforeInvocation (@CacheEvict) | 是否在方法执行前就清空,缺省为 false,如果指定 为 true,则在方法还没有执行的时候就清空缓存, 缺省情况下,如果方法执行抛出异常,则不会清空 缓存 | 例如: @CachEvict(value=”testcache”, beforeInvocation=true) |
| unless (@CachePut) (@Cacheable) | 用于否决缓存的,不像condition,该表达式只在方 法执行之后判断,此时可以拿到返回值result进行判 断。条件为true不会缓存,fasle才缓存 | 例如: @Cacheable(value=”testcache”,unless=”#result == null”) |
Cache SpEL available metadata
| 名字 | 位置 | 描述 | 示例 |
|---|---|---|---|
| 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 |
4、缓存使用
1. 搭建基本环境(MyBatis)
- 导入数据库文件 创建出department和employee表
- 创建javaBean封装数据
- 整合MyBatis操作数据库
- 配置数据源信息
- 使用注解版的MyBatis;
- @MapperScan指定需要扫描的mapper接口所在的包
在配置文件中配置日志级别为debug
#若向数据库执行语句,会输出sql执行的debug信息
#若在缓存中查询则不会,用来判断是否走了缓存
logging.level.包名=debug
# 示例:logging.level.com.atguigu.cache.mapper=debug
#开启自动配置类报告
debug=true
2. 缓存使用
步骤:
- 导入依赖spring-boot-starter-cache
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
-
开启基于注解的缓存 @EnableCaching
-
标注缓存注解即可
- @Cacheable
- @CacheEvict
- @CachePut
默认使用的是ConcurrentMapCacheManager==ConcurrentMapCache;将数据保存在ConcurrentMap<Object, Object>中
开发中使用缓存中间件;redis、memcached、ehcache;
@MapperScan("com.atguigu.cache.mapper") //扫描mapper接口所在包
@SpringBootApplication
@EnableCaching //开启基于注解的缓存
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
3. @Cacheable
-
将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字; -
几个属性:
-
cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
-
key:缓存数据使用的key;可以用它来指定。
-
假设传入id = 1,则 key:1, value:方法的返回值
-
编写SpEL; #id;参数id的值,也可以写成:#a0 #p0 #root.args[0]
@Cacheable(value = {"emp"},key="#root.methodName+'['+#id+']'") //该SpEL表达式指定生成key的形式 ===> getEmp[2] (id=2的时候)(参考上面的Cache SpEL available metadata表格)
-
-
keyGenerator:key的生成器;可以自己指定key的生成器的组件id
key/keyGenerator:二选一使用;
自定义keyGenerator:
//自定义 @Configuration public class MyCacheConfig { @Bean("myKeyGenerator") public KeyGenerator keyGenerator(){ //返回一个KeyGenerator return new KeyGenerator(){ @Override public Object generate(Object target, Method method, Object... params) { return method.getName()+"["+ Arrays.asList(params).toString()+"]"; } }; } } //使用 //EmployeeService.class @Cacheable(value = {"emp"},key="mykeyGenerator")- cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器;二者作用一样,二选一使用
- condition:指定符合条件的情况下才缓存;
condition = “#id>0”:id值>0的时候才进行缓存
condition = “#a0>1”:第一个参数的值>1的时候才进行缓存 - unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
unless = “#result == null”
unless = “#a0==2”:如果第一个参数的值是2,结果不缓存; - sync:是否使用异步模式(默认:false,使用异步模式时不支持unless)
-
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
/**
* @param id
* @return
*/
@Cacheable(value = {"emp"},keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
}
4. 缓存工作原理
- 自动配置类;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;
- 给容器中注册了一个CacheManager:ConcurrentMapCacheManager
- 可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中;
5. @Cacheable的运行流程
- 方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
(CacheManager先获取相应的缓存组件),第一次获取缓存如果没有Cache组件会自动创建。 - 去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
- key是按照某种策略生成的;
- 默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator(keyGenerator的子类)生成key;
- SimpleKeyGenerator生成key的默认策略:
如果没有参数;key=new SimpleKey();
如果有一个参数:key=参数的值
如果有多个参数:key=new SimpleKey(params);
- 没有查到缓存就调用目标方法;
- 将目标方法返回的结果,放进缓存中
@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
6. @CachePut
既调用方法,又更新缓存数据;同步更新缓存
修改了数据库的某个数据,同时更新缓存;
(标注了@CachePut一定会查数据库)
运行时机:
- 先调用目标方法
- 将目标方法的结果缓存起来
@CachePut(value = "emp")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
测试步骤:
-
查询1号员工;查到的结果会放在缓存中;(调用getEmp())
key:1 value:lastName:张三
-
以后查询还是之前的结果
-
更新1号员工;但再次查询的时候发现还是之前的缓存(调用updateEmp())
(原因:默认情况下,是以方法的参数作为key)
将方法的返回值也放进缓存了;
key:传入的employee对象 value:返回的employee对象;
-
查询1号员工?
应该是更新后的员工;
key = “#employee.id”:使用传入的参数的员工id;
key = “#result.id”:使用返回后的id
@Cacheable的key是不能用#result,因为先查询缓存在调用方法,一开始没有返回值也就没有#result
//修改前: @CachePut(value = "emp") //修改后 @CachePut(value = "emp",key="#employee.id")
7. @CacheEvict
缓存清除
- key:指定要清除的数据
- allEntries = true:指定清除这个缓存中所有的数据
- beforeInvocation = false:缓存的清除是否在方法之前执行
- 默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
- beforeInvocation = true:代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
@CacheEvict(value="emp",beforeInvocation = true)
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
//employeeMapper.deleteEmpById(id);
int i = 10/0;
}
8. @Caching
定义复杂的缓存规则
- cacheable 2. put 3. evict
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
@Caching(
cacheable = {
@Cacheable(value="emp",key = "#lastName")
},
put = {
@CachePut(value="emp",key = "#result.id"),
@CachePut(value="emp",key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}
9. @CacheConfig
抽取缓存的公共配置
其他注解中value=“emp”的可以不用再写
@CacheConfig(cacheNames="emp")
@Service
public class EmployeeService {
//……
}
5、整合redis实现缓存
1. 安装并运行redis
#1.安装
docker pull redis
#2.运行
docker run -d -p 6379:6379 --name redis01 redis
2. 搭建redis环境
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Spring Boot的所有starter:
- 配置
spring.redis.host=xxx.xxx.xxx.xxx
3. RedisTemplate&序列化机制
在RedisAutoConfiguration可以看到有两个操作redis的类:
- StringRedisTemplate:操作k-v都是字符串的,因为redis经常都是操作字符串,所以专门抽取出来简化字符串操作
- RedisTemplate:k-v都是对象的
注意:保存的对象必须实现序列化接口
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
template.setConnectionFactory(redisConnectionFactory);
//使用Jackson2JsonRedisSerializer进行序列化
Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(ser);
return template;
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class Springboot01CacheApplicationTests {
@Autowired
EmployeeMapper employeeMapper;
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
RedisTemplate redisTemplate;
@Autowired
//自定义的RedisTemplate,仅改变了序列化机制
RedisTemplate<Object, Employee> empRedisTemplate;
/**
* Redis常见的五大数据类型
* String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
* stringRedisTemplate.opsForValue()[String(字符串)]
* stringRedisTemplate.opsForList()[List(列表)]
* stringRedisTemplate.opsForSet()[Set(集合)]
* stringRedisTemplate.opsForHash()[Hash(散列)]
* stringRedisTemplate.opsForZSet()[ZSet(有序集合)]
*/
@Test
public void test01() {
//给redis中保存数据
stringRedisTemplate.opsForValue().append("msg","hello");
String msg = stringRedisTemplate.opsForValue().get("msg");
System.out.println(msg);
stringRedisTemplate.opsForList().leftPush("mylist", "1");
stringRedisTemplate.opsForList().leftPush("mylist", "2");
}
//测试保存对象
@Test
public void test02() {
Employee empById = employeeMapper.getEmpById(1);
//默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中
redisTemplate.opsForValue().set("emp-01", empById);
//1、将数据以json的方式保存
//(1)自己将对象转为json
//(2)redisTemplate默认的序列化规则;改变默认的序列化规则;
empRedisTemplate.opsForValue().set("emp-01", empById);
}
}
4. 使用缓存&原理
原理:CacheManager生成Cache 缓存组件来实际给缓存中存取数据
-
引入redis的starter,容器中保存的是 RedisCacheManager;
-
RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件;RedisCache通过操作redis缓存数据的
-
默认保存数据 k-v 都是Object;利用序列化保存;如何保存为json —> 更改序列化机制
- 引入了redis的starter,cacheManager变为 RedisCacheManager;
- 默认创建的 RedisCacheManager 操作redis的时候使用的是 RedisTemplate<Object, Object>
- RedisTemplate<Object, Object> 是 默认使用jdk的序列化机制
-
自定义CacheManager;
spring boot 1.x版本:
//自定义CacheManager
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
template.setConnectionFactory(redisConnectionFactory);
//使用Jackson2JsonRedisSerializer进行序列化 ---> 将对象保存成json
Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(ser);
return template;
}
@Bean
public RedisTemplate<Object, Department> deptRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Department> template = new RedisTemplate<Object, Department>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Department> ser = new Jackson2JsonRedisSerializer<Department>(Department.class);
template.setDefaultSerializer(ser);
return template;
}
//CacheManagerCustomizers可以来定制缓存的一些规则
@Primary //将某个缓存管理器作为默认的,有多个CacheManager时必须指定,一般指定原本的为默认的
@Bean
//操作Employee对象的缓存的缓存管理器,不能操作其他对象
public RedisCacheManager employeeCacheManager(RedisTemplate<Object, Employee> empRedisTemplate){
RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate);
//key多了一个前缀
//使用前缀,默认会将CacheName作为key的前缀
cacheManager.setUsePrefix(true);
return cacheManager;
}
@Bean
public RedisCacheManager deptCacheManager(RedisTemplate<Object, Department> deptRedisTemplate){
RedisCacheManager cacheManager = new RedisCacheManager(deptRedisTemplate);
cacheManager.setUsePrefix(true);
return cacheManager;
}
}
spring boot 2.x版本:
这样写一个CacheManager就能更改所有对象的序列化方法,不需要每个对象都写一个
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration cacheConfiguration =
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofDays(1))
.disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();
}
使用缓存的两种方式:
- 注解 @Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig
@Service
public class DeptService {
@Autowired
DepartmentMapper departmentMapper;
@Qualifier("deptCacheManager")
@Autowired
RedisCacheManager deptCacheManager;
/**
* 缓存的数据能存入redis;
* 第二次从缓存中查询就不能反序列化回来;
* 存的是dept的json数据;CacheManager默认使用RedisTemplate<Object, Employee>操作Redis
* 所以要再编写一个操作dept对象缓存的缓存管理器
* @param id
* @return
*/
@Cacheable(cacheNames = "dept",cacheManager = "deptCacheManager")
public Department getDeptById(Integer id){
System.out.println("查询部门"+id);
Department department = departmentMapper.getDeptById(id);
return department;
}
}
- 编码方式
// 使用缓存管理器得到缓存,进行api调用
public Department getDeptById(Integer id){
System.out.println("查询部门"+id);
Department department = departmentMapper.getDeptById(id);
//获取某个缓存
Cache dept = deptCacheManager.getCache("dept");
dept.put("dept:1",department);
return department;
}
本文深入探讨了Spring Boot的缓存机制,包括JSR-107、Spring缓存抽象和Redis整合。详细介绍了@Cacheable、@CachePut、@CacheEvict等注解的使用,并解析了缓存的工作原理及Redis缓存的配置和操作。

1932


被折叠的 条评论
为什么被折叠?



