Spring的缓存抽象
https://blog.csdn.net/zl1zl2zl3/article/details/110987968
面试官:Spring中的@Cacheable缓存注解,你真的了解吗? - 知乎 (zhihu.com)
简介
- 自3.1版以来,Spring 框架提供了对透明地向现有 Spring 应用程序添加缓存的支持。与事务支持类似,缓存抽象允许一致地使用各种缓存解决方案,对代码的影响最小。定义了
org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口来统一不同的缓存技术。并支持使用JCache(JSR-107)
注解简化开发。抽象使得不必编写缓存逻辑,但不提供实际的数据存储。 - 缓存的核心思想是当调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存在缓存中,减少数据访问次数和时间,提高性能。
- 每次调用需要缓存功能的方法时,Spring 会检查指定参数的指定目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
- spring cache是spring框架的缓存抽象,集成了各种主流缓存实现(
ConcurrentMap、redis、ehcache、Caffeine
等) - spring默认使用
ConcurrentMap
作为缓存;如果工程中引入了redis配置,则会使用redis作为缓存(spring.cache.type=redis
) - spring通过CacheManager判断具体使用哪个缓存,每个缓存都有一个具体的CacheManager(比如:EhCacheCacheManager,RedisCacheManager,CaffeineCacheManager),如果没有配置任何的CacheManager,则会使用ConcurrentMap作为缓存
buffer和cache
-
传统上,缓冲区buffer是一个存储区域,用于暂时保存数据,待数据传输速度对齐后再将数据发送出去。在数据传输过程中,如果数据接收速度较快,数据会被存储在缓冲区中,直到接收方准备好接收为止。它可以用来解决数据传输速度不匹配的问题。
-
缓存cache会将经常访问的数据复制到更快的存储介质中,如内存,以便在后续访问时无需再从原始数据源获取。这样能够减少数据访问时间,提高性能。
-
总结
- 用途不同:缓冲主要用于平衡数据传输速度差异,而缓存主要用于提高数据访问速度。
- 数据处理:缓冲不对数据进行处理,只是暂时存储,而缓存可以对数据进行处理以满足特定需求。
- 存储介质:缓冲通常用于暂时存储数据,存储在相同或类似的介质上,而缓存通常将数据存储在更快的存储介质中,如内存。
- 数据类型:缓冲可以用于各种数据类型,包括传输中的数据,而缓存通常用于经常被访问的数据。
Cache/CacheManager接口
- Cache接口包含缓存的各种操作集合,操作缓存就是通过这个接口来操作的。
- Cache 接口下Spring提供了各种 xxxCache 的实现,比如:
RedisCache
、EhCache
、ConcurrentMapCache
- CacheManager 定义了创建、配置、获取、管理和控制多个唯一命名的 Cache。这些 Cache 存在于 CacheManager 的上下文中
使用注意点
要使用缓存抽象,需要注意两个方面:
-
缓存声明: 确定需要缓存的方法及其策略。
-
缓存配置: 存储数据并从中读取数据的后台缓存。
注解
@EnableCaching
用于开启基于注解的缓存,使用 @EnableCaching
标注在 springboot 主启动类上
@SpringBootApplication
@MapperScan("com.aciu.lambdatest.mapper.**")
@EnableCaching
public class LambdaTestApplication {
public static void main(String[] args) {
SpringApplication.run(LambdaTestApplication.class, args);
}
}
@Cacheable
常用于查询方法,能够根据方法的请求参数对其进行缓存
- @Cacheable可以标记在方法和类上面。
- 当标记在方法上表示只对该方法是支持缓存的。
- 当标记在类上面表示该类的所有方法都会支持缓存的。
- @Cacheable 不能使用#result,因为这个是返回结果才能使用,但是@Cacheable在执行方法前就要使用key,所以不行
cacheNames/value
声明缓存名字,可以用数组,声明多个缓存
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
key
-
声明访问缓存的键,由于缓存本质上是键值存储,因此每次调用缓存方法时都需要使用键去访问。
-
缓存数据时使用的 key,可以用它来指定。默认是使用方法参数的值。这个 key 可以使用 spEL 表达式来编写。
-
这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。
-
@Cacheable(cacheNames="books", key="#isbn") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="#isbn.rawNumber") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="#p0") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="#p0.rawNumber") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
-
spEL表达式
keyGenerator
-
key 的生成器,key 和 keyGenerator 二选一使用,如果key的编写有固定模式,推荐使用keyGenerator
-
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
-
@Configuration public class CacheConfig implements WebMvcConfigurer { @Bean("myKeyGenerator") public KeyGenerator keyGenerator(){ return new KeyGenerator(){ @Override public Object generate(Object target, Method method, Object... params){ return method.getName()+"["+ Arrays.asList(params).toString()+"]"; } }; } }
cacheManager
- 可以用来指定缓存管理器。从哪个缓存管理器里面获取缓存。或者cacheResolver指定获取解析器。
cacheResolver
- 即缓存解析器,该属性与cacheManager 是互斥的,只能指定一个。
condition
-
可以用来指定符合条件的情况下才缓存(
condition = “#a0>1”
:第一个参数的值>1的时候才进行缓存) -
@Cacheable(cacheNames="books", condition ="#p0.rawNumber>0") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
unless
- 否定缓存。当 unless 指定的条件为 true ,方法的返回值就不会被缓存。当然也可以获取到结果进行判断。(通过
#result
获取方法结果,如unless="#result==null"
声明返回空值不缓存)
sync
在多线程环境中,某些操作可能被同一个参数并发调用(通常在启动时)。默认情况下,缓存抽象不锁定任何内容,相同的值可能会被多次计算,这与缓存的目的背道而驰。
对于这些特殊情况,可以使用 sync 属性指示基础缓存提供程序在计算值时锁定缓存条目。因此,只有一个线程忙于计算值,而其他线程被阻塞,直到条目在缓存中更新。(sync=true
)
- 如果配置了sync为true,只支持配置一个cacheNames,如果配了多个,就会报错:
- 源码限制
- 1、不支持unless
- 2、只能有一个cache
- 3、不能合并其他与缓存相关的操作
@CachePut
常用于更新/保存方法,会将方法返回值放入缓存
-
每次都会执行方法,并将结果存入指定的缓存中
-
同样可以作用于类和方法上
-
不要在同一个方法上使用@Cacheable和@CachePut
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
@CacheEvict
清空缓存
- 同样可以作用与类和方法上,依旧有@Cacheable的属性,有额外的
allEntries
和beforeInvocation
属性 - 其中,value表示清除操作是发生在哪些Cache上的(对应Cache的名称)
- key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key
- condition表示清除操作发生的条件
allEntries
忽略所有的key键,清除所有缓存,这比一个一个清除元素更有效率。
@CacheEvict(cacheNames="books", allEntries=true)
public void loadBooks(InputStream batch)
beforeInvocation
-
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。
-
使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
@CacheEvict(cacheNames="books", beforeInvocation=true)
public void loadBooks(InputStream batch)
@Caching
- @Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。
- 有时,需要指定同一类型的多个注释(例如@CacheEvict 或@CachePut) ,因为条件或键表达式在不同的缓存之间是不同的。
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
@CacheConfig
- 放在类上,配置该类所有缓存的公共属性,比如设置缓存的名称
- 它允许共享缓存名称、自定义 KeyGenerator、自定义 CacheManager 和自定义 CacheResolver。将此注释放在类上不会打开任何缓存操作。
@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {
@Cacheable
public Book findBook(ISBN isbn) {...}
}