一、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
四、EhCache封裝
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
https://www.jianshu.com/p/154c82073b07