💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 一、概述 应用缓存一般分两种,一是**进程内缓存**,就是使用应用内存的缓存(java应用就是虚拟机的内存);另一个是**进程外缓存**,也就是分布式缓存; 对于单体应用,可使用进程内缓存;但对于分布式应用(包括普通的多进程应用,如appServer,jobServer等并存的情况),需使用进程外分布式缓存; 缓存的使用,必须要谨慎考虑,使用不当会导致数据不一致的问题; ## 二、快速启用 ### **配置项** 平台已经内置了依赖及启用配置,使用的是SpringBoot缓存机制,只需选择缓存类型,无需其他额外配置,可直接使用; 选择了redis做完缓存机制之后,平台提供了具体的redis连接配置选项; ``` rayframework.redis.main.host=ENC(209300309509305308507304600307309) rayframework.redis.main.port=ENC(306309404308) rayframework.redis.main.password=ENC(906211211118906419406409906117904510516317) ``` 可以支持很多种缓存供应商类型,但平台默认选择redis作为缓存供应商,你可以选择其他的缓存供应商,但如果选择外部缓存供应商的话,建议就用redis好了:如果不指定具体其他第三方实现的时候,SpringBoot缓存机制会使用ConcurrentHashMap来作为缓存; >[danger] 注意: >1、平台内置缓存类型配置项`spring.cache.type=redis`与`rayframework.default.cache-provider=redis`不同,后者仅仅是平台自制缓存机制的选项,与这里使用的SpringBoot缓存机制不一样; > 2、平台配置项中`rayframework.redis.second.*`主机及数据库配置,与这里使用的SpringBoot缓存机制的配置完全无关,它们都是平台自制缓存机制选项; > 3、这里使用的SpringBoot缓存机制,仅使用`rayframework.redis.main`的`rayframework.redis.main.database.cache`配置项数据库,除此之外,`rayframework.redis.main`的其他配置项数据库(rayframework.redis.main.database.platform.\*)也都是平台自制缓存机制选项; ### **未使用缓存代码** ``` public interface SysLogDao extends BaseDao<SysLog, Long> { SysLog findByInfoCode(String infoCode); } ``` ``` @RayServiceMethod public ResponseObject view(long logId) { SysLog log = sysLogDao.findByInfoCode("log2022032314482228a"); // SysLog log = sysLogDao.findById(logId).get(); model.setLog(log); return responseObj; } ``` 两次访问该接口,两次都会访问数据库,产生查询语句记录; ### **启用缓存代码** ``` @CacheConfig(cacheNames = "logcache") public interface SysLogDao extends BaseDao<SysLog, Long> { @Cacheable SysLog findByInfoCode(String infoCode); } ``` 两次访问该接口,只有第一次会访问数据库,后面查询,不会产生查询语句记录了,表明缓存奏效了,但是,如果传入的infoCode不同,则还是会去数据库取数据; ``` @RayServiceMethod public ResponseObject view(long logId) { SysLog log = sysLogDao.findByInfoCode("1"); SysLog log2 = sysLogDao.findByInfoCode("2"); SysLog log3 = sysLogDao.findByInfoCode("1"); SysLog log4 = sysLogDao.findByInfoCode("2"); // SysLog log = sysLogDao.findById(logId).get(); model.setLog(log); return responseObj; } ``` 如上,有两个不同的传入参数,总共去数据库查询两次; 查看redis缓存数据的效果: ![](https://img.kancloud.cn/c0/07/c007db92bade30d5aebd95c35e289a09_1895x333.png) ## 三、核心标注 ### **@CacheConfig** 统一配置本类的缓存注解的属性;也可以不使用该注解,直接通过`@Cacheable`自己配置缓存集的名字来定义; ### **@Cacheable** @Cacheable 注解在方法上,表示该方法的返回结果是可以缓存的。也就是说,该方法的返回结果会放在缓存中,以便于以后使用相同的参数调用该方法时,会返回缓存中的值,而不会实际执行该方法。 对于同一个方法,如果参数相同,那么返回结果也是相同的。但是如果参数不同,缓存只能假设结果是不同的,所以对于同一个方法,你的程序运行过程中,使用了多少种参数组合调用过该方法,理论上就会生成多少个缓存的 key; | 参数 | 描述 | | --- | --- | | value | 缓存名称,不能为空,如果使用EHCache,就是ehcache.xml中声明的cache的name| | key |缓存的key,默认为空,支持SpEL| | condition | 触发条件,只有满足条件的情况才会加入缓存,默认为空,既表示全部都加入缓存,支持SpEL| ### **@CachePut** @CachePut 注解的缓存方法总是会执行,而且会尝试将结果放入缓存(当然,是否真的会缓存还跟一些注解参数有关,比如:unless 参数); @CachePut 跟 @Cacheable 有相同的参数属性;与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中; >[danger] > @Cacheable 的逻辑是:查找缓存 - 有就返回 -没有就执行方法体 - 将结果缓存起来; > @CachePut 的逻辑是:执行方法体 - 将结果缓存起来; > 所以 @Cacheable 适用于查询数据的方法,@CachePut 适用于更新数据的方法; ### **@CacheEvict** 标注在需要清除缓存元素的方法或类上,清空缓存;当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作; ## 四、特别注意 1、缓存机制是通过AOP代理实现的,因此,同一个类的内部方法之间调用不走代理,因此,必须确保是通过非内部类来做;建议是把缓存机制单独抽出一层来实现,具体可参考分布式缓存的实例;