前言
快取技術在實際的專案中是必不可少的,合理的利用快取技術能極大的提升網站的訪問速度,提升使用者體驗。 本片文章就介紹如何在spring boot中使用ehcache
這個快取框架。
文章首發於個人部落格:【www.xiongfrblog.cn】
ehcache介紹
在java
中有很多技術都可以實現快取功能,最簡單直接就是使用java
自帶的Map
容器,或者就是使用現有的快取框架,例如memcache
,ehcache
,以及非常熱門的redis
。這裡介紹ehcache
的主要是因為它真的很方便,而且memcache
和redis
都需要額外搭建服務,更適合分散式部署的專案以便於各個模組之間的使用共有的快取內容。而ehcache
主要是記憶體快取,也可以快取到磁碟中,速度快,效率高,功能也強大,適合我們一般的單個專案使用。
spring boot 配置ehcache
在spring boot
中配置ehcahce
主要有以下四步:
pom.xml
中新增依賴- 配置
ehcache.xml
配置檔案 - 開啟快取
- 利用註解使用快取
下面我們詳細介紹每一步。
新增依賴
要想在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
,主要有以下幾個值:user.home
:使用者主目錄user.dir
:使用者當前的工作目錄java.io.tmpdir
:預設臨時路徑ehcache.disk.store.dir
:cache的配置目錄- 自定義絕對路徑
如果對於這幾個目錄不熟悉,可以在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
:設定快取區中的內容是否永久有效,可選值true
或false
,如果選擇true
那麼設定的timeToIdleSeconds
以及timeToLiveSeconds
將失效。 -
maxElementsInMemory
:該快取區中最多可以存放的物件數量,超過這個數量時,會根據overflowToDisk
屬性的值有不同的操作。 -
overflowToDisk
:快取物件超出最大數量時是否啟用磁碟儲存,可選值true
或false
,值為true
時,會將超出的內容快取到磁碟中,為false
時則會根據memoryStoreEvictionPolicy
屬性配置的策略替換掉原來的內容。 -
diskPersistent
:磁碟儲存是否在虛擬機器重啟後持續存在,預設是false
,如果為true
系統在初始化時會將磁碟中的內容載入到快取。 -
timeToIdleSeconds
:設定一個元素在過期前的空閒時間(單位:秒),即訪問該元素的最大間隔時間,超過這個時間該元素就會被清除,預設值為0
,表示一個元素可以無限的空閒。 -
timeToLiveSeconds
:設定一個元素在快取區中的生存時間(單位:秒),即從建立到清除的時間,超過這個時間,該元素就會被清除,預設值為0
,表示一個元素可以無限的儲存。 -
memoryStoreEvictionPolicy
:快取儲存與清除策略。即達到maxElementsInMemory
限制並且overflowToDisk
值為false
時ehcache
就會根據這個屬性的值執行相應的清空策略,該屬性有以下三個值分別代表ehcache
的三種快取清理策略,預設值為LRU
:FIFO
:先進先出策略(First In First Out)
。LFU
:最少被使用(Less Frequently Used)
,所有的快取元素都會有一個屬性記錄該元素被使用的次數,清理元素時最小的那個將會被清除。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
,程式碼中我們對id
為2
的資料做了兩次修改以及兩次查詢操作,並且在執行修改操作時快取了資料,執行該方法之前,id
為2
的資料還不在快取中。
預期效果:執行兩次修改操作,訪問兩次資料庫,兩次查詢操作不訪問資料庫。
[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
,此時快取區中有id
為1
,2
的兩條資料,我們刪除id
為2
的資料,再做查詢操作。
預期效果:刪除資料訪問資料庫一次,並清除快取區中那個相應的資料,因為清除了快取區的內容所以查詢資料會訪問資料庫一次,但是資料庫中相應的內容也已經被刪除,所以查詢不到任何資料。
[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
複製程式碼
日誌輸出的內容符合我們預期。
結語
好了,spring boot
整合ehcache
的內容就到此為止了,下篇部落格再見,bye~~