SpringBoot 的缓存注解实现只能操作单个缓存,并不支持批量。想要实现批量操作,可以引入一个依赖 cache-as-multi。
通过在参数上添加注解 @CacheAsMuti,指明要生成批量缓存key的参数,就可以实现缓存的批量操作。
<dependency>
<groupId>io.github.ms100groupId>
<artifactId>cache-as-multiartifactId>
<version>1.1.3version>
dependency>
本注解需要与下面两套注解搭配使用,以实现对被注解参数所在的方法进行批量的缓存操作。
Spring的缓存注解 @Cacheable
、@CachePut
、@CacheEvict
JSR-107的注解 @CacheResult
、JSR-107的@CachePut
、@CacheRemove
、@CacheKey
只支持 PROXY 模式,不支持 ASPECTJ 模式
假设已有获取单个对象的方法,如下:
class FooService {
public Foo getFoo(Integer fooId) {
//...
}
}
此时如果需要获取批量对象的方法,通常会是下面两种写法:
class FooService {
public Map<Integer, Foo> getMultiFoo(Collection<Integer> fooIds) {
//...
}
public List<Foo> getMultiFoo(List<Integer> fooIds) {
//...
}
}
获取批量对象的方法相对于获取单个对象的方法会有两点变化:
Integer
变为 Collection
或 Set
或 List
。Map
或者 List
。例如 Map
或 List
,若返回的是 List
类型,那应与【对象集合参数】大小相同并顺序一致。在上面例子中,如果需要对获取单个对象的方法做缓存,会使用 @Cacheable
或 @CacheResult
注解: (PS: 这里将 @CacheResult
和 @Cacheable
放在一起举例子,实际使用时通常只用其中的一个)
class FooService {
@Cacheable(cacheNames = "foo")
@CacheResult(cacheName = "foo")
public Foo getFoo(Integer fooId) {
// 用 fooId 生成缓存 key 和计算 condition、unless 条件,用 Foo 为缓存值
}
}
如果对获取批量对象的方法直接加上 @Cacheable
或 @CacheResult
,则会使用【对象集合参数】整体生成一个缓存 key,将返回的 Map
或 List
整体作为一个缓存值。
但通常我们会希望它能变为多个 fooId => Foo
的缓存,即:使用【对象集合参数】中每个【元素】和它对应的值分别作缓存。此时只需要在【对象集合参数】上加上 @CacheAsMulti 注解即可实现我们想要的缓存方式。
class FooService {
@Cacheable(cacheNames = "foo")
@CacheResult(cacheName = "foo")
public Map<Integer, Foo> getMultiFoo(@CacheAsMulti Collection<Integer> fooIds) {
// 为 fooIds 集合中每个元素分别生成缓存 key 和计算 condition、unless 条件,用 Map 中对应的值作为缓存值
}
@Cacheable(cacheNames = "foo")
@CacheResult(cacheName = "foo")
public List<Foo> getMultiFoo(@CacheAsMulti List<Integer> fooIds) {
// 为 fooIds 集合中每个元素分别生成缓存 key 和计算 condition、unless 条件,用 List 中对应的值作为缓存值
// 之后的例子中,返回 List 和 返回 Map 的处理方式都一样,就不再单独举例
}
}
示例如下:
使用 @Cacheable
时 @Cacheable.key()
未配置【或】使用 @CacheResult
时参数中没有 @CacheKey
class FooService {
@Cacheable(cacheNames = "foo", key="")
@CacheResult(cacheName = "foo")
public Foo getFoo(Integer fooId, String arg1) {
// 用 fooId 和 arg1 两个参数生成缓存的 key,用返回值作为缓存值
}
@Cacheable(cacheNames = "foo", key="")
@CacheResult(cacheName = "foo")
public Map<Integer, Foo> getMultiFoo(@CacheAsMulti Collection<Integer> fooIds, String arg1) {
// 用 fooIds 中的每个【元素】分别和 arg1 参数生成缓存的 key,用返回 Map 中【元素】对应的值作为缓存值
}
}
使用 @Cacheable
时 @Cacheable.key()
只配置了【对象参数】的引用【或】使用 @CacheResult
时只有【对象参数】上有 @CacheKey
class FooService {
@Cacheable(cacheNames = "foo", key="#fooId")
@CacheResult(cacheName = "foo")
public Foo getFoo(@CacheKey Integer fooId, String arg1) {
// 用 fooId 生成缓存的 key,用返回值作为缓存值
}
@Cacheable(cacheNames = "foo", key="#fooIds")
@CacheResult(cacheName = "foo")
public Map<Integer, Foo> getMultiFoo(@CacheAsMulti @CacheKey Collection<Integer> fooIds, String arg1) {
// 用 fooIds 中的每个【元素】分别生成缓存的 key,用返回 Map 中【元素】对应的值作为缓存值
}
}
使用 @Cacheable
时 @Cacheable.key()
配置了若干参数的引用【或】使用 @CacheResult
时参数中有若干 @CacheKey
class FooService {
@Cacheable(cacheNames = "foo", key="#fooId+#arg1")
@CacheResult(cacheName = "foo")
public Foo getFoo(@CacheKey Integer fooId, @CacheKey String arg1, Float arg2) {
// 用 fooId 和 arg1 两个参数生成缓存的 key,用返回值作为缓存值
}
@Cacheable(cacheNames = "foo", key="#fooIds+#arg1")
@CacheResult(cacheName = "foo")
public Map<Integer, Foo> getMultiFoo(@CacheAsMulti @CacheKey Collection<Integer> fooIds, @CacheKey String arg1, Float arg2) {
// 用 fooIds 中的每个【元素】分别和 arg1 参数生成缓存的 key,用返回 Map 中【元素】对应的值作为缓存值
// 注意此时【对象集合参数】需要在 Cacheable.key() 中,需要有 @CacheKey 注解
}
}
@CachePut
搭配时,同样符合上面的例子。@CacheEvict
搭配时,若注解的 @CacheEvict.key()
参数中没有 #result
,对【方法】返回类型无要求;若 key 中有 #result
,【方法】返回类型需要是 Map
或 List
。@CachePut
、@CacheEvict
搭配,若 key 参数中已有 #result
, 则可以没有【对象集合参数】的引用。@CacheRemove
搭配时,对【方法】返回类型无要求。@CachePut
搭配时,对【方法】返回类型无要求,可参照下面的示例:单个参数做key,未配置 @CacheKey
:
class FooService {
@CachePut(cacheName = "foo")
public void putFoo(Integer fooId, @CacheValue String value) {
// 用 fooId 参数生成缓存的 key,用 value 作为缓存值
}
@CachePut(cacheName = "foo")
public void putMultiFoo(@CacheAsMulti @CacheValue Map fooIdValueMap) {
// 此时方法的 @CacheValue 参数必须为 Map 类型
// 用 fooIdValueMap 中的每个 Entry 的 key 分别生成缓存的 key,用 Entry 的 value 作为缓存值
}
}
多个参数做key,未配置 @CacheKey
:
class FooService {
@CachePut(cacheName = "foo")
public void putFoo(Integer fooId, String arg1, @CacheValue String value) {
// 用 fooId 和 arg1 两个参数生成缓存的 key,用 value 作为缓存值
}
@CachePut(cacheName = "foo")
public void putMultiFoo(@CacheAsMulti @CacheValue Map fooIdValueMap, String arg1) {
// 此时方法的 @CacheValue 参数必须为 Map 类型
// 用 fooIdValueMap 中的每个 Entry 的 key 分别和 arg1 参数生成缓存的 key,用 Entry 的 value 作为缓存值
}
}
只有【对象参数】上有 @CacheKey
:
class FooService {
@CachePut(cacheName = "foo")
public void putFoo(@CacheKey Integer fooId, String arg1, @CacheValue String value) {
// 用 fooId 参数生成缓存的 key,用 value 作为缓存值
}
@CachePut(cacheName = "foo")
public void putMultiFoo(@CacheAsMulti @CacheKey @CacheValue Map fooIdValueMap, String arg1) {
// 此时方法的 @CacheValue 参数必须为 Map 类型
// 用 fooIdValueMap 中的每个 Entry 的 key 分别生成缓存的 key,用 Entry 的 value 作为缓存值
}
}
若干参数上有 @CacheKey
:
class FooService {
@CachePut(cacheName = "foo")
public void putFoo(@CacheKey Integer fooId, @CacheKey String arg1, String arg2, @CacheValue String value) {
// 用 fooId 和 arg1 两个参数生成缓存的 key,用 value 作为缓存值
}
@CachePut(cacheName = "foo")
public void putMultiFoo(@CacheAsMulti @CacheKey @CacheValue Map fooIdValueMap, @CacheKey String arg1, String arg2) {
// 此时方法的 @CacheValue 参数必须为 Map 类型
// 用 fooIdValueMap 中的每个 Entry 的 key 分别和 arg1 参数生成缓存的 key,用 Entry 的 value 作为缓存值
}
}
@CacheAsMulti
注解不能替代 Spring 缓存注解中的 key 参数,例如:@Cacheable.key()
,也不能替代 @CacheKey
、@CacheValue
注解。KeyGenerator
,则会用【对象集合参数】的每个【元素】和其他参数组成 Object[]
传入 KeyGenerator.generate(Object, Method, Object...)
计算缓存 key;自定义的 CacheKeyGenerator
也一样。Map
,【元素】在 Map
中对应的值为 null
就会缓存 null
,【元素】在 Map
中不存在就不缓存。@CachePut
和 @CacheEvict
搭配,注解的 key 参数配置了 #result
时,若方法的返回类型是 Map
,对于 Map
中不存在的【元素】会使用 null
作为缺省值来计算缓存 key 和 condition、unless 条件。@Cacheable.condition()
、@Cacheable.unless()
等条件表达式是用【对象集合参数】中的每个【元素】分别计算,只将不符合的【元素】排除,而不是整个集合。org.springframework.cache.Cache
接口只定义了单个缓存操作,并不支持批量操作,为此定义了 EnhancedCache
接口扩充了 multiGet
、multiPut
、multiEvict
三个批量操作方法。
当使用某种缓存介质时,需要有对应的 EnhancedCache
接口实现。如果使用的介质没有对应的 EnhancedCache
实现,则会使用默认的 EnhancedCacheConversionService.EnhancedCacheAdapter
进行适配,会使用循环单个操作来实现批量操作,效率较低。同时在对象创建的时候会出现一条 warn 级别的日志。
每种缓存介质还需要定义一个转换器用来将 Cache
自动转为 EnhancedCache
,转换器实现的接口为 EnhancedCacheConverter
。BeanFactory 中注册的转换器会自动加载到 EnhancedCacheConversionService
中用来将 Spring 原有的 Cache
转为 EnhancedCache
。
包里已经实现了 RedisCache
、ConcurrentMapCache
、Ehcache
、caffeineCache
的 EnhancedCache
接口和相应的转换器,具体可到 cache.convert.converter
下查看。
OperationSource
和 Interceptor
的 Bean 定义,使用自定义的(继承原有的)来替换。OperationSource
查询构建 Operation
后,查询构建 MultiOperation
并缓存。Interceptor
执行拦截前,查询是否缓存有对应的 MultiOperation
,如果有则拦截执行。EnhancedCache
扩充 Spring 的 Cache
。EnhancedCacheConverter
将 Cache
转为 EnhancedCache
。Cache
的实现类,增加 EnhancedCache
和 EnhancedCacheConverter
的实现类。EnhancedCacheConversionService
将所有 EnhancedCacheConverter
(包括使用者自定义的)自动注入进来。EnhancedCacheResolver
包装 CacheResolver
,并注入 EnhancedCacheConversionService
,在调用 resolveCaches
获取 Cache
时将其转换为 EnhancedCache
。GenericTypeResolver
处理泛型类ReflectionUtils
反射工具类ResolvableType
处理各种字段类型、返回类型、参数类型AnnotationUtils
注解工具类,例如向上找父类的注解AnnotatedElementUtils
被注释的元素工具类,例如查找合并注解MergedAnnotations
合并注解的操作工具@AliasFor
注解参数别名就是利用上述的 Spring 注解工具实现的。Aware
的处理需要显示的实现,例如在 BeanPostProcessor
的实现中。Map
没有对应的 Set
实现,可以用 Collections.newSetFromMap(new ConcurrentHashMap<>(16))
。@Autowried
注解的是 AutowiredAnnotationBeanPostProcessor
。ApplicationContextAware
接口的是 ApplicationContextAwareProcessor
。针对不同的缓存缓存方式单独配置。例如Redis:
spring:
cache:
redis:
time-to-live: PT15M #缓存15分钟
前提:Spring 为缓存注解中每一个配置的 CacheName,都生成一个单独的 Cache 对象。
通常可以通过下面三种方式来实现:
CacheManager
或 CacheResolver
。@CacheAsMulti
),例如 JetCache。只要实现 RedisCacheManagerBuilderCustomizer
接口,就可以在 RedisCacheManager
生成前,设置 RedisCacheManagerBuilder
实现自定义配置。
具体实现查看
RedisCacheCustomizer
类。
之后只需增加如下配置:
spring:
cache:
redis:
time-to-live: PT15M #默认缓存15分钟
cache-as-multi: #下面是 cache-as-multi 的配置
serialize-to-json: true #使用 RedisSerializer.json() 序列化
cache-name-time-to-live-map: #cacheName对应的缓存时间
foo: PT15S #foo缓存15秒
demo: PT5M #demo缓存5分钟
github