SpringBoot配置Ehcache缓存
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP API等特点。
主要的特性有:
-
快速
-
简单
-
多种缓存策略
-
缓存数据有两级:内存和磁盘,因此无需担心容量问题
-
缓存数据会在虚拟机重启的过程中写入磁盘
-
可以通过RMI、可插入API等方式进行分布式缓存
-
具有缓存和缓存管理器的侦听接口
-
支持多缓存管理器实例,以及一个实例的多个缓存区域
-
提供Hibernate的缓存实现
二、Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术;
-
Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
-
Cache接口下spring提供了各种xxxCache的实现,比如EhCacheCache、RedisCache等等
-
每次调用需要缓存功能的方法时,Spring会检查指定参数的指定目标方法是否已经被调用过;如果有缓存就直接从缓存中获取结果,没有就调用方法并缓存结果后返回给用户。下次调用则直接从缓存中获取。
1、缓存注解概念
Cache | 缓存接口,定义缓存操作。实现有:EhCacheCache、RedisCache等等 |
---|---|
CacheManager | 缓存管理器,管理各种缓存组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存 |
@EnableCaching | 开启基于注解的缓存 |
三、SpringBoot 添加 EhCache缓存
1、pom.xml 添加依赖
<!--ehcache 缓存--> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
2、resources目录下创建ehcache.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"> <!-- 磁盘缓存位置 --> <diskStore path="E:\data"/> <!-- 默认缓存 --> <defaultCache maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </defaultCache> <!-- cache 可以设置多个,例如localCache、UserCache和HelloWorldCache --> <cache name="localCache" eternal="true" maxElementsInMemory="100" maxElementsOnDisk="1000" overflowToDisk="true" diskPersistent="true" timeToIdleSeconds="0" timeToLiveSeconds="0" memoryStoreEvictionPolicy="LRU"/> <cache name="UserCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="10" timeToLiveSeconds="10" overflowToDisk="false" memoryStoreEvictionPolicy="LRU"/> <!-- hello world缓存 --> <cache name="HelloWorldCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="5" timeToLiveSeconds="5" overflowToDisk="false" memoryStoreEvictionPolicy="LRU"/> <!-- memoryStoreEvictionPolicy Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)--> <!-- 缓存配置 name:缓存名称。 maxElementsInMemory:缓存最大个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 maxElementsOnDisk:硬盘最大缓存个数。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 --> </ehcache>
3、创建测试方法测试CacheManager
@Test void cacheManagerTest() { // 1. 创建缓存管理器 CacheManager cacheManager = CacheManager.create(this.getClass().getResourceAsStream("/ehcache.xml")); // 2. 获取ehcache.xml 中定义的 HelloWorldCache 缓存对象 Cache cache = cacheManager.getCache("HelloWorldCache"); // 3. 创建元素 Element element = new Element("key1", "value1"); // 4. 将元素添加到缓存 cache.put(element); // 5. 获取缓存 Element value = cache.get("key1"); System.out.println(value); System.out.println(value.getObjectKey()); System.out.println(value.getObjectValue()); // 6. 删除元素 cache.remove("key1"); System.out.println(cache.getSize()); // 7. 刷新缓存 cache.flush(); // 8. 关闭缓存管理器 cacheManager.shutdown(); }
运行结果如下:
[ key = key1, value=value1, version=1, hitCount=1, CreationTime = 1630287141405, LastAccessTime = 1630287141406 ]
key1
value1
0
1、封装CacheManagerHelper
package com.example.ehcachedemo.helper; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; /* * Ehcache 缓存管理对象单例 * 磁盘 + 内存 * */ public class CacheManagerHelper { private final String EHCAHE_PATH = "/ehcache.xml"; private final String CACHE_NAME = "localCache"; private CacheManager manager; private Cache cache; private CacheManagerHelper() { init(); } private static class SingletonInstance { private static final CacheManagerHelper singleton = new CacheManagerHelper(); } public static CacheManagerHelper getInstance() { return SingletonInstance.singleton; } /** * 每次开始使用缓存对象需要初始化 */ public void init() { manager = CacheManager.create(this.getClass().getResourceAsStream(EHCAHE_PATH)); cache = manager.getCache(CACHE_NAME); } /** * 把key放入缓存中 */ public void put(String key, Object value) { cache.put(new Element(key, value)); flush(); } /** * 根据key获取缓存元素 */ public Object get(String key) { Element element = cache.get(key); return element != null ? element.getObjectValue() : null; } /** * 根据key移除缓存 */ public void remove(String key) { cache.remove(key); flush(); } /** * 构建内存与磁盘的关系 */ public void flush() { cache.flush(); } /** * 关闭缓存管理器 */ public void shutdown() { manager.shutdown(); } }
2、新建User类和UserController测试CacheManagerHelper
User:
package com.example.ehcachedemo.bean; public class User { public User() { } public User(String userId, String name, int age) { this.userId = userId; this.name = name; this.age = age; } private String userId; private String name; private int age; //region getter and setter public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //endregion @Override public String toString() { return "User{" + "userId='" + userId + '\'' + ", name='" + name + '\'' + ", age=" + this.age + '}'; } }
UserController
package com.example.ehcachedemo.controller; import com.example.ehcachedemo.bean.User; import com.example.ehcachedemo.helper.CacheManagerHelper; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/user") public class UserController { /** * 通过CacheManagerHelper获取缓存信息 * @param id 访问id * */ @GetMapping("byId/{id}") public String getUserById(@PathVariable String id) { String result = (String) CacheManagerHelper.getInstance().get(id); if (result == null) { User user = new User(id, "张三", (int) (Math.random() * 100)); result = user.toString(); CacheManagerHelper.getInstance().put(id, result); } return result; } }
3、封装EhCacheConfiguration类,用于注解方式使用Ehcache
package com.example.ehcachedemo.configuration; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.ehcache.EhCacheCacheManager; import org.springframework.cache.ehcache.EhCacheManagerFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; @Configuration @EnableCaching public class EhCacheConfiguration { @Bean public EhCacheManagerFactoryBean cacheManagerFactoryBean() { EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean(); bean.setConfigLocation(new ClassPathResource("ehcache.xml")); bean.setShared(true); return bean; } @Bean public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean bean) { return new EhCacheCacheManager(bean.getObject()); } }
Application启动类需要加入 @EnableCaching
4、UserController 注解方式使用EhCache
/** * 通过注解方式,如果缓存存在,则从缓存获取数据,否则从方法体获取,并更新到缓存中 * @param id 访问id * */ @GetMapping("ByIdWithInject/{id}") @Cacheable(value = "localCache", key = "#id") public String getUserByIdWithInject(@PathVariable String id) { User user = new User(id, "张三", (int) (Math.random() * 100)); return user.toString(); }
5、@Cacheable、@CachePut、@CacheEvict的使用
在Service使用@Cacheable、@CachePut、@CacheEvict
package com.example.ehcachedemo.service; import com.example.ehcachedemo.bean.User; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import java.util.Date; @Service @Component public class UserServiceImpl implements UserService { /** * @param userId * @return * @Cacheable: 1.方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取; * 2.去Cache中查找缓存的内容,使用一个key,默认就是方法的参数; * 3.没有查到缓存就调用目标方法 * 4.将目标方法返回的结果,放进缓存中 * 5.condition:指定符合条件的情况下才缓存; */ @Cacheable(value = "UserCache", key = "#userId", condition = "#userId!=''") @Override public User findById(String userId) { System.out.println(new Date().getTime() + "进入UserService.findById,当前userId为:" + userId); return new User(userId, "张三", (int) (Math.random() * 100)); } /** * @param user * @return * @CachePut:既调用方法,又更新缓存数据;同步更新缓存 运行时机: * 1、先调用目标方法 * 2、将目标方法的结果缓存起来 */ @CachePut(value = "UserCache", key = "#user.userId") @Override public User updateUser(User user) { System.out.println(new Date().getTime() + "进入UserService.updateUser,当前userId为:" + userId); return new User(userId, "张三", (int) (Math.random() * 100)); } /** * @param userId * @CacheEvict:缓存清除 key:指定要清除的数据 * beforeInvocation = true:代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除 */ @CacheEvict(value = "UserCache", key = "#userId", beforeInvocation = true) @Override public void deleteUser(String userId) { System.out.println(new Date().getTime() + "进入UserService.deleteUser,当前userId为:" + userId); } }
测试代码
@Autowired UserService userService; @Test void userTest() throws InterruptedException { // 1.新增 User user1 = new User("123", "张三", (int) (Math.random() * 100)); userService.updateUser(user1); System.out.println("初始:" + user1); // 2.查询 user1 = userService.findById("123"); System.out.println("查询:" + user1); // 3.清除 userService.deleteUser("123"); System.out.println("已清除缓存"); // 4.查询 user1 = userService.findById("123"); System.out.println("查询:" + user1); System.out.println("休眠10秒后重新查询"); Thread.sleep(10 * 1000); // 休眠10秒,测试是否过期重新获取 user1 = userService.findById("123"); System.out.println(user1); }
测试结果
1630291225178进入UserService.updateUser,当前userId为:123
初始:User{userId='123', name='张三', age=82}
查询:User{userId='123', name='张三', age=82}
1630291225232进入UserService.deleteUser,当前userId为:123
已清除缓存
1630291225232进入UserService.findById,当前userId为:123
查询:User{userId='123', name='张三', age=21}
休眠10秒后重新查询
1630291235234进入UserService.findById,当前userId为:123
User{userId='123', name='张三', age=13}
五、参考文档
https://blog.csdn.net/huang_wei_cai/article/details/105293166