Spring boot學習之Spring boot配置ehcache緩存框架

前言

緩存技術在實際的項目中是必不可少的,合理的利用緩存技術能極大的提升網站的訪問速度,提升用戶體驗。 本片文章就介紹如何在spring boot中使用ehcache這個緩存框架。

ehcache介紹

java中有很多技術都可以實現緩存功能,最簡單直接就是使用java自帶的Map容器,或者就是使用現有的緩存框架,例如memcache,ehcache ,以及非常熱門的redis。這裏介紹ehcache的主要是因爲它真的很方便,而且memcacheredis都需要額外搭建服務,更適合分佈式部署的項目以便於各個模塊之間的使用共有的緩存內容。而ehcache主要是內存緩存,也可以緩存到磁盤中,速度快,效率高,功能也強大,適合我們一般的單個項目使用。

spring boot 配置ehcache

spring boot中配置ehcahce主要有以下四步:

  1. pom.xml中添加依賴
  2. 配置ehcache.xml配置文件
  3. 開啓緩存
  4. 利用註解使用緩存

下面我們詳細介紹每一步。

添加依賴

要想在spring boot中使用緩存,首先需要開啓緩存,然後添加ehcache的依賴,所以我們在pom.xml中添加如下連個依賴項:

<!--開啓緩存-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- EhCache -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>
複製代碼

編寫配置文件

添加了依賴之後,spring boot會自動默認加載src/mian/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="java.io.tmpdir"/>
	 
	 <!-- 默認配置 -->
	 <defaultCache eternal="false"
	    maxElementsInMemory="1000"
	    overflowToDisk="false"
	    diskPersistent="false"
	    timeToIdleSeconds="0"
	    timeToLiveSeconds="600"
	    memoryStoreEvictionPolicy="LRU"/>
	    
	 <!-- 自定義配置 -->
	 <cache name="userCache"
	   eternal="false"
	   maxElementsInMemory="1000"
	   overflowToDisk="false"
	   diskPersistent="false"
	   timeToIdleSeconds="0"
	   timeToLiveSeconds="600"
	   memoryStoreEvictionPolicy="LRU"/>
</ehcache>
複製代碼

下面介紹樣例中出現的三個節點:

  • <diskStore>:這個節點是非必須的,只有在使用了磁盤存儲的情況下才需要配置,表示緩存文件在磁盤中保存的路徑,該路徑通過path屬性來指定,磁盤緩存使用的文件後綴名是*.data*.index,主要有以下幾個值:
    1. user.home:用戶主目錄
    2. user.dir:用戶當前的工作目錄
    3. java.io.tmpdir:默認臨時路徑
    4. ehcache.disk.store.dir:cache的配置目錄
    5. 自定義絕對路徑

如果對於這幾個目錄不熟悉,可以在java中獲取,如下:

public static void main(String[] args) {
	System.out.println(System.getProperty("user.home"));
	System.out.println(System.getProperty("user.dir"));
	System.out.println(System.getProperty("java.io.tmpdir"));
}
複製代碼

下面是我本機打印出來的路徑,僅做參考:

C:\Users\Administrator
D:\Program Data\eclipse-workspace\springboot-ehcache
C:\Users\Administrator\AppData\Local\Temp\
複製代碼

這裏需要注意一點,要想某個對象被緩存到磁盤中,需要該對象實現序列化接口。

  • <ehcache>:自定義緩存區,可以有零個或者多個,重要屬性如下:

    • name:緩存區名字,必須屬性,用來區分緩存區的唯一標識。

    • eternal:設置緩存區中的內容是否永久有效,可選值truefalse,如果選擇true那麼設置的timeToIdleSeconds以及timeToLiveSeconds將失效。

    • maxElementsInMemory:該緩存區中最多可以存放的對象數量,超過這個數量時,會根據overflowToDisk屬性的值有不同的操作。

    • overflowToDisk:緩存對象超出最大數量時是否啓用磁盤保存,可選值truefalse,值爲true時,會將超出的內容緩存到磁盤中,爲false時則會根據memoryStoreEvictionPolicy屬性配置的策略替換掉原來的內容。

    • diskPersistent:磁盤存儲是否在虛擬機重啓後持續存在,默認是false,如果爲true系統在初始化時會將磁盤中的內容加載到緩存。

    • timeToIdleSeconds:設置一個元素在過期前的空閒時間(單位:秒),即訪問該元素的最大間隔時間,超過這個時間該元素就會被清除,默認值爲0,表示一個元素可以無限的空閒。

    • timeToLiveSeconds:設置一個元素在緩存區中的生存時間(單位:秒),即從創建到清除的時間,超過這個時間,該元素就會被清除,默認值爲0,表示一個元素可以無限的保存。

    • memoryStoreEvictionPolicy:緩存存儲與清除策略。即達到maxElementsInMemory限制並且overflowToDisk值爲falseehcache就會根據這個屬性的值執行相應的清空策略,該屬性有以下三個值分別代表ehcache的三種緩存清理策略,默認值爲LRU

      1. FIFO:先進先出策略(First In First Out)
      2. LFU:最少被使用(Less Frequently Used),所有的緩存元素都會有一個屬性記錄該元素被使用的次數,清理元素時最小的那個將會被清除。
      3. LRU:最近最少使用(Least Resently Used),所有緩存的元素都會有一個屬性記錄最後一次使用的時間,清理元素時時間最早的那個元素將會被清除。
    • diskExpiryThreadIntervalSeconds:磁盤緩存的清理線程運行間隔,默認是120秒。

    • diskSpoolBufferSizeMB:設置磁盤緩存區的大小,默認爲30MB。

    • maxEntriesLocalDisk:設置磁盤緩存區最多能存放元素的數量。

  • <defaultCache>:默認緩存區,即是一個name屬性爲default<ehcache>節點,屬性和<ehcache>節點都一樣,一個ehcache.xml文件中只能有一個<defaultCache>節點,當我們沒有自定義的<ehcache>時,默認使用該緩存區。

對於defaultCache這裏有需要注意的地方,因爲他是一個特殊的,所以我們在自定義緩存區的時候不能再定義名爲default的,並且在使用的時候也不能通過value=default來指定默認的緩存區。

這裏補充一點,項目中如果不想使用默認的路徑以及名字我們也可以自定義ehcache配置文件的名字以及路徑,在application.properties配置文件中配置如下內容:

#後邊的路徑可以自己指定
spring.cache.ehcache.config=classpath:ehcache.xml
複製代碼

開啓緩存

spring boot中開啓緩存非常簡單,只需要在在啓動類上添加一個@EnableCaching註解即可。

使用註解

spring boot中使用ehcache緩存主要是通過註解來使用,而且我們一般在service實現層使用緩存功能,常用的註解如下:

@Cacheable

該註解主要用在方法上邊,每當程序進入被該註解標記的方法時,系統會首先判斷緩存中是否存在相同key的元素,如果存在就直接返回緩存區中存放的值,並且不會執行方法的內容,如果不存在就執行該方法,並且判斷是否需要將返回值添加到緩存區中,常用屬性:

  • value:指定使用哪個緩存區,就是我們在配置文件裏邊配置的<ehcache>節點的name屬性對應的值,可以指定多個值。
    //指定一個
    @Cacheable(value="userCache")
    //指定多個
    @Cacheable(value={"userCache","userCache2"})
    複製代碼
  • key:緩存元素的key,需要按照SpEL表達式編寫,這個我們一般按照指定方法的參數來確定。
    //#p0表示將第一個參數當成key,也可以直接寫參數名字例如:#id,兩者表達意思一樣
    @Cacheable(value="userCache",key="#p0")
    public SysUser getById(Integer id){//內容省略...};
    複製代碼
  • condition:添加緩存的條件,需要按照SpEL表達式編寫,僅當該屬性返回true時才添加緩存。
     //僅當id>10時才緩存
     @Cacheable(value="userCache",key="#p0",condition="#p0>10")
     public SysUser getById(Integer id){//內容省略...};
    複製代碼

@CachePut

該註解主要用在方法上邊,能夠根據方法的參數以及返回值以及自定義的條件判斷是否添加緩存,該註解標記的方法一定會執行,其屬性與@Cacheable一致。

@CachePut(value="userCache",key="#entity.id")
public SysUser insertSysuser(SysUser entity) {
	// TODO Auto-generated method stub		
    //省略內容
}
複製代碼

@CacheEvict

該註解主要用在方法上邊,能根據條件對緩存進行清空,常用屬性如下:

  • value:同上
  • key:同上
  • condition:同上
  • allEntries:是否清空所有緩存內容,默認爲false,如果設置爲true,那麼在方法執行完成之後並且滿足condition條件時會清空該緩存區的所有內容。
  • beforeInvocation:清除內容操作是否發生在方法執行之前,默認爲false,表示清除操作在方法執行完之後再進行,如果方法執行過程中拋出異常,那麼清除操作就不執行,如果爲true,則表示在方法執行之前執行清除操作。
@CacheEvict(value="userCache",key="#p0",allEntries=false, beforeInvocation=true)
public int deleteByPrimarykey(Integer key) {
	// TODO Auto-generated method stub
    //省略內容
}
複製代碼

效果測試

上邊介紹了spring boot配置ehcache的步驟,接下來測試緩存效果,本項目在整合了Mybatis以及日誌框架的前提下進行,基本的代碼就不貼出來了,直接給出最關鍵的service實現層以及controller的代碼:

SysuserServiceImpl.java

package com.web.springbootehcache.service.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import com.web.springbootehcache.dao.SysUserMapper;
import com.web.springbootehcache.entity.SysUser;
import com.web.springbootehcache.service.IsysUserService;

/**
* @author Promise
* @createTime 2019年3月19日 
* @description
*/
@Service("sysuserService")
public class SysUserServiceImpl implements IsysUserService{
	
	private final static Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class);
	
	@Autowired
	private SysUserMapper sysuserMapper;

	@Override
	@Cacheable(value="userCache",key="#p0")
	public SysUser fingByPrimarykey(Integer key) {
		// TODO Auto-generated method stub
		log.debug("去數據庫查詢了數據!");
		return sysuserMapper.selectByPrimaryKey(key);
	}

	@Override
	@CachePut(value="userCache",key="#p0.id")
	public SysUser updateSysuser(SysUser entity) {
		// TODO Auto-generated method stub
		log.debug("更新了數據庫數據!");
		int res = sysuserMapper.updateByPrimaryKey(entity);
		if(res >0)
			return entity;
		else
			return null;
	}

	@Override
	@CachePut(value="userCache",key="#entity.id")
	public SysUser insertSysuser(SysUser entity) {
		// TODO Auto-generated method stub		
		int res = sysuserMapper.insert(entity);
		log.debug("新增了數據!id爲:{}",entity.getId());
		if(res >0)
			return entity;
		else
			return null;
	}

	@Override
	@CacheEvict(value="userCache",key="#p0",beforeInvocation=true)
	public int deleteByPrimarykey(Integer key) {
		// TODO Auto-generated method stub
		log.debug("刪除了數據!");
		return sysuserMapper.deleteByPrimaryKey(key);
	}

}

複製代碼

該類中給出了基本的CRUD操作對應的緩存操作,當然不是絕對的,實際使用中根據自己需要改動。

IndexController.java

package com.web.springbootehcache.controller;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.web.springbootehcache.entity.SysUser;
import com.web.springbootehcache.service.IsysUserService;

/**
* @author Promise
* @createTime 2019年3月19日
* @description
*/
@RestController
public class IndexController {
	
	private final static Logger log = LoggerFactory.getLogger(IndexController.class);
	
	@Autowired
	private IsysUserService sysuserService;

	@RequestMapping(value="/select/{id}")
	public Object index(@PathVariable Integer id) {
		Map<String, Object> map = new HashMap<>();
		SysUser sysuser = sysuserService.fingByPrimarykey(id);
		log.debug("查詢了id爲:{}的用戶信息!",sysuser.getId());
		SysUser sysuser2 = sysuserService.fingByPrimarykey(id);
		log.debug("查詢了id爲:{}的用戶信息!",sysuser2.getId());
		map.put("res", sysuser);
		return map;
	}
	
	@RequestMapping(value="/update")
	public Object update() {
		Map<String, Object> map = new HashMap<>();
		//第一次修改
		SysUser sysuser = new SysUser(1, "eran", "eran1", 20, "M");
		sysuserService.updateSysuser(sysuser);
		//第一次查詢
		sysuser = sysuserService.fingByPrimarykey(1);
		log.debug("查詢了id爲:{}的用戶信息!",sysuser.getId());
		//第2次修改
		sysuser = new SysUser(1, "eran", "eran2", 20, "M");
		sysuserService.updateSysuser(sysuser);
		//第2次查詢
		sysuser = sysuserService.fingByPrimarykey(1);
		log.debug("查詢了id爲:{}的用戶信息!",sysuser.getId());
 		map.put("res", sysuser);
		return map;
	}
	
	@RequestMapping(value="/insert")
	public Object insert() {
		Map<String, Object> map = new HashMap<>();
		SysUser sysuser = new SysUser();
		sysuser.setName("admin");
		sysuser.setAge(22);
		sysuser.setPass("admin");
		sysuser.setSex("M");
		sysuserService.insertSysuser(sysuser);
		//查詢
		sysuser = sysuserService.fingByPrimarykey(sysuser.getId());
		map.put("res", sysuser);
		return map;
	}
	
	@RequestMapping(value="/delete/{id}")
	public Object delete(@PathVariable Integer id) {
		Map<String, Object> map = new HashMap<>();
		sysuserService.deleteByPrimarykey(id);
		//查詢
		SysUser sysuser = sysuserService.fingByPrimarykey(id);
		map.put("res", sysuser);
		return map;
	}
}

複製代碼

數據庫測試數據

 

在這裏插入圖片描述

 

 

啓動項目,訪問localhost:1188/select/1,控制檯日誌如下:

預期效果:執行查詢操作兩次,訪問數據庫一次。

[default]2019-03-20 17:35:05,287 [http-nio-1188-exec-2 32] DEBUG >> 去數據庫查詢了數據! >> c.w.s.s.i.SysUserServiceImpl
[default]2019-03-20 17:35:05,327 [http-nio-1188-exec-2 110] INFO >> HikariPool-1 - Starting... >> c.z.h.HikariDataSource
[default]2019-03-20 17:35:05,332 [http-nio-1188-exec-2 68] WARN >> Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation. >> c.z.h.u.DriverDataSource
[default]2019-03-20 17:35:06,106 [http-nio-1188-exec-2 123] INFO >> HikariPool-1 - Start completed. >> c.z.h.HikariDataSource
[default]2019-03-20 17:35:06,113 [http-nio-1188-exec-2 159] DEBUG >> ==>  Preparing: select id, `name`, pass, sex, age from sys_user where id = ?  >> c.w.s.d.S.selectByPrimaryKey
[default]2019-03-20 17:35:06,141 [http-nio-1188-exec-2 159] DEBUG >> ==> Parameters: 1(Integer) >> c.w.s.d.S.selectByPrimaryKey
[default]2019-03-20 17:35:06,176 [http-nio-1188-exec-2 159] DEBUG >> <==      Total: 1 >> c.w.s.d.S.selectByPrimaryKey
[default]2019-03-20 17:35:06,185 [http-nio-1188-exec-2 33] DEBUG >> 查詢了id爲:1的用戶信息! >> c.w.s.c.IndexController
[default]2019-03-20 17:35:06,186 [http-nio-1188-exec-2 35] DEBUG >> 查詢了id爲:1的用戶信息! >> c.w.s.c.IndexController
複製代碼

可以很直白的看出,我們執行了兩次查詢操作,但是從數據庫中取數據的操作就執行了一次,可見還有一次直接從緩存中取數據,達到了我們預期的效果。

訪問localhost:1188/update,代碼中我們對id2的數據做了兩次修改以及兩次查詢操作,並且在執行修改操作時緩存了數據,執行該方法之前,id2的數據還不在緩存中。

預期效果:執行兩次修改操作,訪問兩次數據庫,兩次查詢操作不訪問數據庫。

[default]2019-03-20 17:47:37,254 [http-nio-1188-exec-1 40] DEBUG >> 更新了數據庫數據! >> c.w.s.s.i.SysUserServiceImpl
[default]2019-03-20 17:47:37,291 [http-nio-1188-exec-1 110] INFO >> HikariPool-1 - Starting... >> c.z.h.HikariDataSource
[default]2019-03-20 17:47:37,299 [http-nio-1188-exec-1 68] WARN >> Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation. >> c.z.h.u.DriverDataSource
[default]2019-03-20 17:47:37,953 [http-nio-1188-exec-1 123] INFO >> HikariPool-1 - Start completed. >> c.z.h.HikariDataSource
[default]2019-03-20 17:47:37,964 [http-nio-1188-exec-1 159] DEBUG >> ==>  Preparing: update sys_user set `name` = ?, pass = ?, sex = ?, age = ? where id = ?  >> c.w.s.d.S.updateByPrimaryKey
[default]2019-03-20 17:47:38,006 [http-nio-1188-exec-1 159] DEBUG >> ==> Parameters: eran(String), eran1(String), M(String), 20(Integer), 2(Integer) >> c.w.s.d.S.updateByPrimaryKey
[default]2019-03-20 17:47:38,104 [http-nio-1188-exec-1 159] DEBUG >> <==    Updates: 1 >> c.w.s.d.S.updateByPrimaryKey
[default]2019-03-20 17:47:38,237 [http-nio-1188-exec-1 48] DEBUG >> 查詢了id爲:2的用戶信息! >> c.w.s.c.IndexController
[default]2019-03-20 17:47:38,239 [http-nio-1188-exec-1 40] DEBUG >> 更新了數據庫數據! >> c.w.s.s.i.SysUserServiceImpl
[default]2019-03-20 17:47:38,239 [http-nio-1188-exec-1 159] DEBUG >> ==>  Preparing: update sys_user set `name` = ?, pass = ?, sex = ?, age = ? where id = ?  >> c.w.s.d.S.updateByPrimaryKey
[default]2019-03-20 17:47:38,243 [http-nio-1188-exec-1 159] DEBUG >> ==> Parameters: eran(String), eran2(String), M(String), 20(Integer), 2(Integer) >> c.w.s.d.S.updateByPrimaryKey
[default]2019-03-20 17:47:38,286 [http-nio-1188-exec-1 159] DEBUG >> <==    Updates: 1 >> c.w.s.d.S.updateByPrimaryKey
[default]2019-03-20 17:47:38,287 [http-nio-1188-exec-1 54] DEBUG >> 查詢了id爲:2的用戶信息! >> c.w.s.c.IndexController
複製代碼

結果符合我們預期。

新增操作和更新操作原理一樣都是使用@CachePut註解,這裏就不重複演示,直接測試刪除數據清除相應緩存功能,訪問localhost:1188/delete/2,此時緩存區中有id1,2的兩條數據,我們刪除id2的數據,再做查詢操作。

預期效果:刪除數據訪問數據庫一次,並清除緩存區中那個相應的數據,因爲清除了緩存區的內容所以查詢數據會訪問數據庫一次,但是數據庫中相應的內容也已經被刪除,所以查詢不到任何數據。

[default]2019-03-20 17:57:28,337 [http-nio-1188-exec-4 64] DEBUG >> 刪除了數據! >> c.w.s.s.i.SysUserServiceImpl
[default]2019-03-20 17:57:28,341 [http-nio-1188-exec-4 159] DEBUG >> ==>  Preparing: delete from sys_user where id = ?  >> c.w.s.d.S.deleteByPrimaryKey
[default]2019-03-20 17:57:28,342 [http-nio-1188-exec-4 159] DEBUG >> ==> Parameters: 2(Integer) >> c.w.s.d.S.deleteByPrimaryKey
[default]2019-03-20 17:57:28,463 [http-nio-1188-exec-4 159] DEBUG >> <==    Updates: 1 >> c.w.s.d.S.deleteByPrimaryKey
[default]2019-03-20 17:57:28,464 [http-nio-1188-exec-4 32] DEBUG >> 去數據庫查詢了數據! >> c.w.s.s.i.SysUserServiceImpl
[default]2019-03-20 17:57:28,467 [http-nio-1188-exec-4 159] DEBUG >> ==>  Preparing: select id, `name`, pass, sex, age from sys_user where id = ?  >> c.w.s.d.S.selectByPrimaryKey
[default]2019-03-20 17:57:28,468 [http-nio-1188-exec-4 159] DEBUG >> ==> Parameters: 2(Integer) >> c.w.s.d.S.selectByPrimaryKey
[default]2019-03-20 17:57:28,494 [http-nio-1188-exec-4 159] DEBUG >> <==      Total: 0 >> c.w.s.d.S.selectByPrimaryKey
複製代碼

日誌輸出的內容符合我們預期。

原文地址:https://juejin.im/post/5c9212faf265da610e5ec8b8?utm_source=gold_browser_extension

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章