SpringBoot系列(7)---SpringBoot-Cache(EhCache)
SpringBoot提供数据缓存的功能,相信非常多人已经用过cache了。因为数据库的IO瓶颈应该大家也吃过不少亏了,所以一般情况下我们都会引入非常多的缓存策略,例如引入redis,引入hibernate的二级缓存等等。
SpringBoot在annotation的层面给我们实现了cache,当然这也是得益于Spring的AOP。所有的缓存配置只是在annotation层面配置,完全没有侵入到我们的代码当中,就像我们的声明式事务一样。
Spring定义了CacheManager和Cache接口统一不同的缓存技术。其中CacheManager是Spring提供的各种缓存技术的抽象接口。而Cache接口包含缓存的各种操作,当然我们一般情况下不会直接操作Cache接口。
Spring针对不同的缓存技术,需要实现不同的cacheManager,Spring定义了如下的cacheManger实现
CacheManger | 描述 |
SimpleCacheManager | 使用简单的Collection来存储缓存,主要用于测试 |
ConcurrentMapCacheManager | 使用ConcurrentMap作为缓存技术(默认) |
NoOpCacheManager | 测试用 |
EhCacheCacheManager | 使用EhCache作为缓存技术,以前在hibernate的时候经常用 |
GuavaCacheManager | 使用google guava的GuavaCache作为缓存技术 |
HazelcastCacheManager | 使用Hazelcast作为缓存技术 |
JCacheCacheManager | 使用JCache标准的实现作为缓存技术,如Apache Commons JCS |
RedisCacheManager | 使用Redis作为缓存技术 |
当然常规的SpringBoot已经为我们自动配置了EhCache、Collection、Guava、ConcurrentMap等缓存,默认使用SimpleCacheConfiguration,即使用ConcurrentMapCacheManager。SpringBoot的application.properties配置文件,使用spring.cache前缀的属性进行配置。
spring.cache.type=#缓存的技术类型 spring.cache.cache-names=应用程序启动创建缓存的名称 spring.cache.ehcache.config=ehcache的配置文件位置 spring.cache.infinispan.config=infinispan的配置文件位置 spring.cache.jcache.config=jcache配置文件位置 spring.cache.jcache.provider=当多个jcache实现类时,指定选择jcache的实现类
在SpringBoot环境下我们需要导入相关缓存技术的依赖,并在配置类当中配置@EnableCaching开启缓存技术。
我们这里不适用默认的ConcurrentMapCache 而是使用 EhCache
所以我在resources目录下创建了ehcache.xml的配置文件,然后在application.properties 设置type为ehcache(intellij有明确的提示):
ehcache.xml:
<ehcache> <!-- 指定一个文件目录,当EHCache把数据写到硬盘上时,将把数据写到这个文件目录下 --> <diskStore path="java.io.tmpdir"/> <!-- 设定缓存的默认数据过期策略 --> <cache name="weibo" maxElementsInMemory="10000" /> <defaultCache maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="10" timeToLiveSeconds="120" diskPersistent="false" memoryStoreEvictionPolicy="LRU" diskExpiryThreadIntervalSeconds="120"/> <!-- maxElementsInMemory 内存中最大缓存对象数,看着自己的heap大小来搞 --> <!-- eternal:true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false --> <!-- maxElementsOnDisk:硬盘中最大缓存对象数,若是0表示无穷大 --> <!-- overflowToDisk:true表示当内存缓存的对象数目达到了maxElementsInMemory界限后, 会把溢出的对象写到硬盘缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。--> <!-- diskSpoolBufferSizeMB:磁盘缓存区大小,默认为30MB。每个Cache都应该有自己的一个缓存区。--> <!-- diskPersistent:是否缓存虚拟机重启期数据 --> <!-- diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认为120秒 --> <!-- timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后, 如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期, EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0, 则表示对象可以无限期地处于空闲状态 --> <!-- timeToLiveSeconds:设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后, 如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期, EHCache将把它从缓存中清除。只有当eternal属性为false,该属性才有效。如果该属性值为0, 则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有意义 --> <!-- memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时, Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、 FIFO(先进先出)、LFU(最少访问次数)。--> </ehcache>
application.properties:
spring.cache.type=ehcache spring.cache.ehcache.config=ehcache.xml在配置类配置@EnableCaching
@SpringBootApplication @EnableCaching public class DemoApplication extends WebMvcConfigurerAdapter {
然后说说4个annotation的配置:
@Cacheable 在方法执行前Spring先是否有缓存数据,如果有直接返回。如果没有数据,调用方法并将方法返回值存放在缓存当中。
@CachePut 无论怎样,都将方法的范湖值放到缓存当中。
@CacheEvict 将一条或者多条数据从缓存中删除。
@Caching 可以通过@Caching注解组合多个注解集合在一个方法上
使用演示JPA时候的方法进行缓存测试:
@Transactional @CachePut(value = "weibo",key="#weibo.weiboId") public Weibo saveWeibo(Weibo weibo){ this.weiboRepository.save(weibo); return weibo; } @Cacheable(value = "weibo") public Weibo getWeiboById(long id){ return this.weiboRepository.getByWeiboId(id); } @Transactional @CacheEvict(value = "weibo",key = "#weibo.weiboId") public void remove(Weibo weibo){ this.weiboRepository.delete(weibo); }
当然如果我们想单独配置一下weibo这个缓存可以通过ehcache.xml进行单独配置,不过需要提醒的是 maxElementsInMemory属性配置是必须的,否则无法启动SpringBoot应用。
<cache name="weibo" maxElementsInMemory="10000" overflowToDisk="false" timeToIdleSeconds="60" timeToLiveSeconds="120" />
通过Controller进行测试,缓存生效:
@RestController @RequestMapping("/cache") public class CacheTestController { @Autowired private WeiboService weiboService; @Autowired private UserRepository userRepository; @RequestMapping("/getWeibo/{id}") public Weibo getWeibo(@PathVariable("id") long id){ return weiboService.getWeiboById(id); } @RequestMapping("/addWeibo") public Weibo addWeibo(String username,String weiboText){ User user = userRepository.getByUsernameIs(username); Weibo weibo = new Weibo(user,weiboText,new Date(System.currentTimeMillis())); return this.weiboService.saveWeibo(weibo); } @RequestMapping("/delete/{id}") public Weibo delete(@PathVariable("id") long id){ Weibo weibo = this.weiboService.getWeiboById(id); this.weiboService.remove(weibo); return weibo; } }