天天看點

SpringBoot配置Ehcache緩存

一、Ehcache介紹

EhCache 是一個純Java的程序内緩存架構,具有快速、精幹等特點,是Hibernate中預設CacheProvider。Ehcache是一種廣泛使用的開源Java分布式緩存。主要面向通用緩存,Java EE和輕量級容器。它具有記憶體和磁盤存儲,緩存加載器,緩存擴充,緩存異常處理程式,一個gzip緩存servlet過濾器,支援REST和SOAP API等特點。

主要的特性有:

  1. 快速
  2. 簡單
  3. 多種緩存政策
  4. 緩存資料有兩級:記憶體和磁盤,是以無需擔心容量問題
  5. 緩存資料會在虛拟機重新開機的過程中寫入磁盤
  6. 可以通過RMI、可插入API等方式進行分布式緩存
  7. 具有緩存和緩存管理器的偵聽接口
  8. 支援多緩存管理器執行個體,以及一個執行個體的多個緩存區域
  9. 提供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