SpringBoot Cache cache
Hello everyone, I am thinking about it.
Recently, I am reviewing SpringBoot related source code. Found a lot of previous problems that could have a more elegant solution.
Just like Cache cache, don't many people think it. Integrating Mybatis, only the second-level cache of Mybatis can operate? Or just access a cache database like Redis.
In fact, SpringBoot's SimpleCache is also a memory-based caching technology, suitable for caching a small amount of data
- Buffer frequently queried data
- Buffering data that is computationally expensive
- Record the number of user visits to limit the flow of the interface flow control
- Record form submission status to prevent duplicate submissions.
Before I knew enough about SpringBoot Cache, I might encapsulate a Map and cache it myself.
But I found that SpringBoot Cache supports so many caches
SpringBoot-cache loading order
When we do not introduce dependencies such as JCache and Redis, SpringBoot uses SimpleCache as a cache by default, and its underlying layer uses ConcurrentMap as a container to store cached content, and has a complete API
SimpleCache property ConcurrentMap
So yeah~
Taoism is not enough, you have to learn~
enter text
First simply use SpringBoot to initialize and create an SSM project to implement a table CRUD. This is not the point.
Add annotations to the SpringBoot startup class @EnableCaching
and enable caching
You can find the SpringBoot automatic configuration class by intercepting the breakpoint org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
(this step is related to the Spring automatic configuration class. If you don’t know how to find it, you can read the "Spring Automatic Configuration Class" related information or find me)
SpringBoot Cache loading order
We found CacheAutoConfiguration
that introducing CacheConfigurationImportSelector.class
CacheAutoConfiguration.java
CacheConfigurationImportSelector
是 CacheAutoConfiguration
中第一个内部类,它实现了 ImportSelector
重写了 selectImports
方法,那就是要重新的自动配置一下
我们就可以找到文章开头的那张图,在不引入其他 cache 的情况下,默认采用 SimpleCache 的放松
springboot-cache 加载顺序
SimpleCacheConfiguration
里面只有一个 @Bean 注解,就是返回一个 ConcurrentMapCacheManager
而 ConcurrentMapCacheManager
里面就有我们之前看到的
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
还有一个最重要的方法, Cache getCache(String name)
,值得一提的是 ConcurrentMapCacheManager
并不是 SpringBoot 中的源码,而是 Spring-context 中的。
Cacheable 运行流程
简单看一下,就是先到 cacheMap (ConcurrentMap) 中获取一下。没获取到就进入业务逻辑进行查询
目标代码
文字描述一下运行流程
- 方法运行之前,先去查询 Cache 缓存组件,安装 CacheName 指定的名字获取,第一次获取缓存没有 Cache 组件时,就去创建
createConcurrentMapCache(name)
。 - 去 Cache 中查找缓存内容,使用 key 默认就是方法参数,如果是多个参数,总是会根据一种策略生成一种 key
- 没有查到缓存就调用目标方法
- 然后将目标方法结果放入缓存中
一句话总结: @Cacheable 标注的方法执行前会查询换成中是否有这个数据,默认按照参数作为key进行查询,如果没有调用目标方法将结果放入缓存,以后在调用时,直接使用缓存中数据。
除了 @Cacheable 注解之外呢,还有其他两种常用注解
@CachePut(Update)
即调用方法,又更新缓存,一般用于更新操作。
运行流程:
- 先调用目标方法
- 将目标方法结果缓存起来
CachePut 案例
@CacheEvict(Delete)
清除缓存,需要指定名字和key
属性:
- value / cacheNames 缓存名字
- key 缓存键
- allEntrles 是否清除这个缓存 value 中的所有 key,如果为 true 则清除所有 key(与 key 属性二选一)
- beforelnvocation 默认 false,执行方法后再清除缓存,如果设置为 true 则执行目标方法前清除缓存 作用:可能目标方法存在异常 实际数据清除了,但因为别的事务导致异常,方法执行失败,缓存就没有删除,(数据库已删除,缓存未删除)
CacheEvict 案例
基于 Redis 的缓存
第一步启动一个 Redis 然后项目中添加 redis 依赖
启动 Redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
通过断点老办法,我们可以找到 RedisAutoConfiguration
RedisAutoConfiguration
熟悉的感觉来了,一个 redisTemplate
还有一个 stringRedisTemplate
其实 stringRedisTemplate
就是继承了 redisTemplate
SimpleRedisTemplate 继承 RedisTemplate
简单的介绍了一下 SpringBoot 自动配置 Redis 相关的源码之后,迅速的写一个例子吧
缓存顺利的加载到 Redis 中了
没有 Redis 之前使用的 SimpleCache ,使用了 仅仅是因为加入了 Reids 的依赖,缓存就顺利的写到了 Redis 里,这是因为 SpringBoot 自动配置顺序有关:
我们可以看到 RedisCacheConfiguration
高于 SimpleCacheConfiguration
的
难道是 SpringBoot 一个一个扫描,发现一个之后就 continue 吗?当然不是了,SpringBoot 就玩的很优雅
我们打开原先的 SimpleCacheConfiguration
可以看到注解上有这样一段代码
@ConditionalOnMissingBean(CacheManager.class)
@Conditional
的意思是如果存在或者 (类、注解...) 的情况下,就加载此配置类,这样是 SpringBoot 自动化配置的一个核心点!
那 @ConditionalOnMissingBean
的意思是,如果存在这个类,就不让这个自动配置类生效。我们再根据上图的加载优先顺序,每个配置类中,都有这样一句话。也都有一个 cacheManager 的 Bean
@Bean
ConcurrentMapCacheManager cacheManager()...
This means that whoever creates CacheManager
the Cache configuration class that is subsequently loaded cannot be instantiated.
The design is very ingenious, isn't it? This idea is reflected in other places in SpringBoot!
Now that we already know how SpringBoot creates CacheManager by autoloading, of course we can also customize CacheManger in the same way
The default configuration of Redis is to use the serialization that comes with JDK to cache the value value, we can modify this configuration in the same way
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换
RedisSerializer<String> strSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jacksonSeial =
new Jackson2JsonRedisSerializer(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
// 定制缓存数据序列化方式及时效
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
// 设置有效期为1天
.entryTtl(Duration.ofDays(1))
// 设置 key 的序列化方式为 String
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(strSerializer))
// 设置 value 的序列化方式为 Json
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(jacksonSeial)) .disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager .builder(redisConnectionFactory).cacheDefaults(config).build();
return cacheManager;
}
Take another look at the effect!
Thanks for reading!