<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
<dependency>
<groupId>net.sf.ehcachegroupId>
<artifactId>ehcacheartifactId>
dependency>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
defaultCache>
<cache name="users"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
cache>
ehcache>
文件中参数说明:
diskStore
:为缓存路径,ehcache
分为内存
和磁盘
两级,此属性定义磁盘的缓存位置。参数解释如下:
user.home
:用户主目录user.dir
:用户当前工作目录java.io.tmpdir
:默认临时文件路径defaultCache
和cache
都是用来指定缓存的,defaultCache
只能有一个,如果在使用时,没有指定具体缓存名字就用这个defaultCache
默认缓存,cache
缓存可以有多个,但是里面的name
必须是不同名字cache
标签
name
:缓存名称maxElementsInMemory
:缓存最大数目maxElementsOnDisk
:硬盘最大缓存个数。eternal
:对象是否永久有效,一但设置了,timeout
将不起作用。overflowToDisk
:是否保存到磁盘,当系统宕机时保存磁盘timeToIdleSeconds
:设置对象在失效前的允许闲置时间(单位:秒
)。仅当eternal=false
对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。timeToLiveSeconds
:设置对象在失效前允许存活时间(单位:秒
)。最大时间介于创建时间
和失效时间
之间。仅当eternal=false对
象不是永久有效时使用,默认是0,也就是对象存活时间无穷大。diskPersistent
:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.diskSpoolBufferSizeMB
:这个参数设置DiskStore
(磁盘缓存)的缓存区大小。默认是30MB
。每个Cache都应该有自己的一个缓冲区。diskExpirTyhreadIntervalSeconds
:磁盘失效线程运行时间间隔,默认是120秒。memoryStoreEvictionPolicy
:当达到maxElementsInMemory
限制时,Ehcache
将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)
。也可以设置为FIFO(先进先出
)或是LFU(较少使用)
。clearOnFlush
:内存数量最大时是否清除。memoryStoreEvictionPolicy
:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。添加注解@EnableCaching
,说明开启注解缓存
@SpringBootApplication
@EnableCaching
@MapperScan("cn.jzh.mapper")
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
spring.application.name=CacheDemo
server.port=8080
#server.servlet.context-path= /
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test
spring.datasource.username=test
spring.datasource.password=test
#连接类型说明为druid
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#扫描mapper.xml文件
mybatis.mapper-locations=classpath:mapper/*Mapper.xml
#开启驼峰命名的意思是查询出来的对象里面的有下划线的字段自动转换为驼峰命名形式,并和实体去匹配
mybatis.configuration.map-underscore-to-camel-case=true
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
spring.cache.type=ehcache
spring.cache.ehcache.cofnig=ehcache.xml
@Cacheable
注解在方法上,表示该方法的返回结果是可以缓存的。也就是说,该方法的返回结果会放在缓存中,以便于以后使用相同的参数
调用该方法时,会返回缓存中的值,而不会实际执行该方法。
注意
:这里强调了一点:参数相同
。
这一点应该是很容易理解的,因为缓存不关心方法的执行逻辑,它能确定的是:对于同一个方法,如果参数相同,那么返回结果也是相同的。但是如果参数不同,缓存只能假设结果是不同的,所以对于同一个方法,程序运行过程中,使用了多少种参数组合调用过该方法,理论上就会生成多少个缓存的 key(当然,这些组合的参数指的是与生成 key 相关的)
@Cacheable
这个注解常用的几个属性:
cacheNames/value
:用来指定缓存组件的名字,二者选其一即可,若不指定则用默认缓存@Cacheable("menu")
@Cacheable
支持同一个方法关联多个缓存。这种情况下,当执行方法之前,这些关联的每一个缓存都会被检查,而且只要至少其中一个缓存命中了,那么这个缓存中的值就会被返回@Cacheable({"menu", "menuById"})
key
:缓存数据时使用的key
,可以用它来指定。默认是使用方法参数的值。(这个 key 你可以使用 spEL 表达式来编写)keyGenerator
:key 的生成器。 key 和 keyGenerator 二选一使用
cacheManager
:可以用来指定缓存管理器。从哪个缓存管理器里面获取缓存。condition
:可以用来指定符合条件的情况下才缓存@Cacheable(value="menu",condition="#id>1")
unless
:否定缓存。当 unless
指定的条件为 true ,方法的返回值就不会被缓存。当然也可以获取到结果进行判断。(通过 #result
获取方法结果)@Cacheable(value="menu",unless="#id>1")
@Cacheable(value = {"menuById"}, key = "#id", unless = "#result.type == 'folder'")
sync
:是否使用异步模式true/false
。在一个多线程的环境中,某些操作可能被相同的参数并发地调用,这样同一个 value
值可能被多次计算(或多次访问 db),这样就达不到缓存的目的。针对这些可能高并发的操作,我们可以使用 sync
参数来告诉底层的缓存提供者将缓存的入口锁住,这样就只能有一个线程计算操作的结果值,而其它线程需要等待,这样就避免了 n-1 次数据库访问。sync = true
可以有效的避免缓存击穿的问题。需要提前注意指定key
,需要以#
打头
官方说 key
和 keyGenerator
参数是互斥的,同时指定两个会导致异常
keyGenerator
:@Cacheable
时如果不指定 key
参数,则该缓存名下的所有 key
会使用 KeyGenerator
根据参数自动生成。spring
有一个默认的 SimpleKeyGenerator
,在 spring boot
自动化配置中,这个会被默认注入。key 生成器
要求参数具有有效的 hashCode() 和 equals()
方法实现。另外,keyGenerator
也支持自定义, 并通过 keyGenerator
来指定SimpleKey.EMPTY
SimpleKey
key
:KeyGenerator
生成,spring
官方更推荐显式指定 key 的方式,即指定 @Cacheable
的 key
参数。key
的值还是需要根据参数的不同来生成,那么如何实现动态拼接呢?SpEL(Spring Expression Language,Spring 表达式语言) 能做到这一点。显示指定key,实现动态拼接
@Cacheable(value = "users",key = "#root.methodName+'[' + #id +']'")
public Object queryUserList(Integer id){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("id",1);
List<User> users = userMapper.selectList(wrapper);
return JSON.toJSON(users);
}
自定义keyGenerator
import org.springframework.cache.interceptor.KeyGenerator;
import java.lang.reflect.Method;
import java.util.Arrays;
@Configuration
public class MyCacheConfig{
@Bean("myKeyGenerator ")
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toArray()+"]";
}
};
}
}
自定义keyGenerator
使用例子
@Cacheable(value = "users",keyGenerator = "myKeyGenerator")
public Object queryUserList(Integer id){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("id",1);
List<User> users = userMapper.selectList(wrapper);
return JSON.toJSON(users);
}
前面说过,缓存的 key 支持使用 spEL 表达式去编写,下面总结一下使用 spEL 去编写 key 可以用的一些元数据
名字 | 位置 | 描述 | 示例 |
---|---|---|---|
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({"menu", "menuById"}) ,则有两个cache | #root.caches[0].name |
argument name | evaluation context | 方法参数名字,可以直接使用 #参数名,也可以使用#p0或者#a0形式,0代表参数索引 | #a0 |
result | evaluation context | 方法执行后的返回值(仅当方法执行后的判断有效) | #result |
CacheManager
,缓存管理器是用来管理(检索)一类缓存的。通常来讲,缓存管理器是与缓存组件类型相关联的。我们知道,spring
缓存抽象的目的是为使用不同缓存组件类型提供统一的访问接口,以向开发者屏蔽各种缓存组件的差异性。那么 CacheManager
就是承担了这种屏蔽的功能。spring 为其支持的每一种缓存的组件类型提供了一个默认的 manager
,如:RedisCacheManager 管理 redis 相关的缓存的检索、EhCacheManager 管理 ehCache 相关的缓等。
CacheResolver
,缓存解析器是用来管理缓存管理器的,CacheResolver
保持一个 cacheManager
的引用,并通过它来检索缓存。CacheResolver 与 CacheManager
的关系有点类似于 KeyGenerator 跟 key
。spring 默认提供了一个 SimpleCacheResolver
,开发者可以自定义并通过 @Bean
来注入自定义的解析器,以实现更灵活的检索。
大多数情况下,我们的系统只会配置一种缓存,所以我们并不需要显式指定 cacheManager 或者 cacheResolver
。但是 spring
允许我们的系统同时配置多种缓存组件,这种情况下,我们需要指定。指定的方式是使用 @Cacheable
的 cacheManager
或者 cacheResolver
参数。
注意
:按照官方文档,cacheManager
和 cacheResolver
是互斥参数,同时指定两个可能会导致异常
当需要在不影响方法执行的情况下更新缓存时,可以使用@CachePut
,也就是说,被 @CachePut
注解的缓存方法总是会执行,而且会尝试将结果放入缓存(当然,是否真的会缓存还跟一些注解参数有关,比如:unless 参数)。@CachePut
跟 @Cacheable
有相同的参数属性(但是没有 sync
属性)。@CachePut
更加适合于缓存填充,而不是方法执行流的优化。
由于与 @Cacheable
的属性基本相同,所以不再重复示例。这里重点说明一下它们的区别:
@Cacheable
的逻辑是:查找缓存->有就返回->没有就执行方法体->将结果缓存起来;@CachePut
的逻辑是:执行方法体 -> 将结果缓存起来;所以 @Cacheable
适用于查询数据的方法,@CachePut
适用于更新数据的方法。
除了填充缓存,spring cache
也支持使用 @CacheEvict
来删除缓存。@CacheEvict
就是一个触发器,在每次调用被它注解的方法时,就会触发删除它指定的缓存的动作。跟 @Cacheable
和 @CachePut
一样,@CacheEvict
也要求指定一个或多个缓存,也指定自定义一的缓存解析器和 key
生成器,也支持指定条件(condition 参数)。
@CacheEvict
是用来清除缓存的,有以下属性:
value/cacheNames
:缓存位置名称,不能为空key
:缓存的key,默认为空(如果allEntries=false,清除指定key的缓存)condition
:触发条件,只有满足条件的情况才会清除缓存,默认为空,支持SpELallEntries
:true表示清除value中的全部缓存,默认为falsebeforeInvocation
:是否在执行对应方法之前删除缓存,默认 false
(即执行方法之后再删除缓存)beforeInvocation
是 @CacheEvict
中特有的一个属性,意为是否在执行对应方法之前删除缓存,默认 false
(即执行方法之后再删除缓存)。
首先思考一个问题,在什么情况下我们需要主动去删除缓存呢?一般来讲都是在删除数据的时候,需要主动去删除缓存。那么就存在一个问题,程序执行时顺序的,那我们到底是应该先删除缓存,再调用方法去数据库中删除;还是先从数据库中删除,完了之后再去删除对应的缓存呢?
在正常情况下,这两种方式差别并不大,毕竟程序执行都是毫秒级的,顺序执行没有什么时间跨度。但是,现实环境复杂,缓存访问和 db 访问都可能会出现异常,这种情况下就有区别了:
至于该如何取舍,spring cache
通过 beforeInvocation
给开发者提供选择