SpringBoot缓存之整合Ehcache

1 Ehcache

点此了解Ehcache原理

1.1 pom.xml

<dependency>
 	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<!-- ehcache坐标 -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>

1.2 ehcache.xml

1.2.1 示例

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <diskStore path="java.io.tmpdir"/>

    <!--defaultCache:echcache 的默认缓存策略 -->
    <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>

1.2.2 参数说明

文件中参数说明:

  • diskStore:为缓存路径,ehcache分为内存磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
    • user.home:用户主目录
    • user.dir :用户当前工作目录
    • java.io.tmpdir:默认临时文件路径
  • defaultCachecache都是用来指定缓存的,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(最少访问次数)。

1.3 启动类和配置文件

1.3.1 修改启动类

添加注解@EnableCaching,说明开启注解缓存

@SpringBootApplication
@EnableCaching
@MapperScan("cn.jzh.mapper")
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}    

1.3.2 配置文件

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

1.4 缓存数据

1.4.1 @Cacheable

@Cacheable注解在方法上,表示该方法的返回结果是可以缓存的。也就是说,该方法的返回结果会放在缓存中,以便于以后使用相同的参数调用该方法时,会返回缓存中的值,而不会实际执行该方法。

注意:这里强调了一点:参数相同
这一点应该是很容易理解的,因为缓存不关心方法的执行逻辑,它能确定的是:对于同一个方法,如果参数相同,那么返回结果也是相同的。但是如果参数不同,缓存只能假设结果是不同的,所以对于同一个方法,程序运行过程中,使用了多少种参数组合调用过该方法,理论上就会生成多少个缓存的 key(当然,这些组合的参数指的是与生成 key 相关的)

1.4.1.1 常见属性

@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")
    执行后判断,不缓存的条件。unless 接收一个结果为 true 或 false 的表达式,表达式支持 SpEL。当结果为 true 时,不缓存
    示例:@Cacheable(value = {"menuById"}, key = "#id", unless = "#result.type == 'folder'")
  • sync :是否使用异步模式
    是否同步,true/false。在一个多线程的环境中,某些操作可能被相同的参数并发地调用,这样同一个 value 值可能被多次计算(或多次访问 db),这样就达不到缓存的目的。针对这些可能高并发的操作,我们可以使用 sync 参数来告诉底层的缓存提供者将缓存的入口锁住,这样就只能有一个线程计算操作的结果值,而其它线程需要等待,这样就避免了 n-1 次数据库访问。
    sync = true可以有效的避免缓存击穿的问题。

1.4.1.2 key & keyGenerator

需要提前注意指定key,需要以#打头
官方说 keykeyGenerator 参数是互斥的,同时指定两个会导致异常

  • keyGenerator:
    当我们在声明 @Cacheable 时如果不指定 key 参数,则该缓存名下的所有 key 会使用 KeyGenerator根据参数自动生成。spring有一个默认的 SimpleKeyGenerator ,在 spring boot自动化配置中,这个会被默认注入。
    默认的 key 生成器要求参数具有有效的 hashCode() 和 equals() 方法实现。另外,keyGenerator 也支持自定义, 并通过 keyGenerator 来指定
    生成规则如下:
    • 如果该缓存方法没有参数,返回 SimpleKey.EMPTY
    • 如果该缓存方法有一个参数,返回该参数的实例 ;
    • 如果该缓存方法有多个参数,返回一个包含所有参数的 SimpleKey
  • key:
    相较于使用 KeyGenerator 生成,spring 官方更推荐显式指定 key 的方式,即指定 @Cacheablekey 参数。
    即便是显式指定,但是 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);
    }

1.4.1.3 使用spEL编写key

前面说过,缓存的 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

1.4.1.4 cacheManager & cacheResolver

CacheManager,缓存管理器是用来管理(检索)一类缓存的。通常来讲,缓存管理器是与缓存组件类型相关联的。我们知道,spring 缓存抽象的目的是为使用不同缓存组件类型提供统一的访问接口,以向开发者屏蔽各种缓存组件的差异性。那么 CacheManager 就是承担了这种屏蔽的功能。spring 为其支持的每一种缓存的组件类型提供了一个默认的 manager,如:RedisCacheManager 管理 redis 相关的缓存的检索、EhCacheManager 管理 ehCache 相关的缓等。

CacheResolver,缓存解析器是用来管理缓存管理器的,CacheResolver 保持一个 cacheManager 的引用,并通过它来检索缓存。CacheResolver 与 CacheManager 的关系有点类似于 KeyGenerator 跟 key。spring 默认提供了一个 SimpleCacheResolver,开发者可以自定义并通过 @Bean 来注入自定义的解析器,以实现更灵活的检索。

大多数情况下,我们的系统只会配置一种缓存,所以我们并不需要显式指定 cacheManager 或者 cacheResolver。但是 spring 允许我们的系统同时配置多种缓存组件,这种情况下,我们需要指定。指定的方式是使用 @CacheablecacheManager 或者 cacheResolver 参数。

注意:按照官方文档,cacheManagercacheResolver 是互斥参数,同时指定两个可能会导致异常

1.4.2 @CachePut

当需要在不影响方法执行的情况下更新缓存时,可以使用@CachePut,也就是说,被 @CachePut 注解的缓存方法总是会执行,而且会尝试将结果放入缓存(当然,是否真的会缓存还跟一些注解参数有关,比如:unless 参数)。@CachePut@Cacheable 有相同的参数属性(但是没有 sync 属性)。@CachePut 更加适合于缓存填充,而不是方法执行流的优化。

由于与 @Cacheable 的属性基本相同,所以不再重复示例。这里重点说明一下它们的区别:

  • @Cacheable 的逻辑是:查找缓存->有就返回->没有就执行方法体->将结果缓存起来;
  • @CachePut 的逻辑是:执行方法体 -> 将结果缓存起来;

所以 @Cacheable 适用于查询数据的方法,@CachePut 适用于更新数据的方法。

1.5 清除缓存@CacheEvict

1.5.1 常见属性

除了填充缓存,spring cache也支持使用 @CacheEvict 来删除缓存。@CacheEvict就是一个触发器,在每次调用被它注解的方法时,就会触发删除它指定的缓存的动作。跟 @Cacheable@CachePut 一样,@CacheEvict 也要求指定一个或多个缓存,也指定自定义一的缓存解析器和 key 生成器,也支持指定条件(condition 参数)。

@CacheEvict是用来清除缓存的,有以下属性:

  • value/cacheNames:缓存位置名称,不能为空
  • key:缓存的key,默认为空(如果allEntries=false,清除指定key的缓存)
    一般来说,我们的更新操作只需要刷新缓存中某一个值,所以定义缓存的key值的方式就很重要,最好是能够唯一,因为这样可以准确的清除掉特定的缓存,而不会影响到其它缓存值
  • condition:触发条件,只有满足条件的情况才会清除缓存,默认为空,支持SpEL
  • allEntries:true表示清除value中的全部缓存,默认为false
  • beforeInvocation:是否在执行对应方法之前删除缓存,默认 false(即执行方法之后再删除缓存)

1.5.2 beforeInvocation

beforeInvocation@CacheEvict 中特有的一个属性,意为是否在执行对应方法之前删除缓存,默认 false(即执行方法之后再删除缓存)。
首先思考一个问题,在什么情况下我们需要主动去删除缓存呢?一般来讲都是在删除数据的时候,需要主动去删除缓存。那么就存在一个问题,程序执行时顺序的,那我们到底是应该先删除缓存,再调用方法去数据库中删除;还是先从数据库中删除,完了之后再去删除对应的缓存呢?

在正常情况下,这两种方式差别并不大,毕竟程序执行都是毫秒级的,顺序执行没有什么时间跨度。但是,现实环境复杂,缓存访问和 db 访问都可能会出现异常,这种情况下就有区别了:

  • 如果先删除缓存成功,然后 db 删除失败,那么接下来的查询就会直达数据库,造成压力;
  • 如果先 db 删除成功,然后删除缓存失败,那么就会造成脏缓存;

至于该如何取舍,spring cache 通过 beforeInvocation 给开发者提供选择

posted @   上善若泪  阅读(415)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2021-11-14 从jar包中读取资源文件(原理深究)
2021-11-14 Queue之ArrayDeque源码解析
2021-11-14 Stack&Vector源码解析
点击右上角即可分享
微信分享提示