緩存技術在實際的項目中是必不可少的,合理的利用緩存技術能極大的提高網站的訪問速度,提高用戶體驗。 本片文章就介紹如何在spring boot中使用ehcache
這個緩存框架。php
文章首發於我的博客:【www.xiongfrblog.cn】html
在java
中有不少技術均可以實現緩存功能,最簡單直接就是使用java
自帶的Map
容器,或者就是使用現有的緩存框架,例如memcache
,ehcache
,以及很是熱門的redis
。這裏介紹ehcache
的主要是由於它真的很方便,並且memcache
和redis
都須要額外搭建服務,更適合分佈式部署的項目以便於各個模塊之間的使用共有的緩存內容。而ehcache
主要是內存緩存,也能夠緩存到磁盤中,速度快,效率高,功能也強大,適合咱們通常的單個項目使用。java
在spring boot
中配置ehcahce
主要有如下四步:mysql
pom.xml
中添加依賴ehcache.xml
配置文件下面咱們詳細介紹每一步。web
要想在spring boot
中使用緩存,首先須要開啓緩存,而後添加ehcache
的依賴,因此咱們在pom.xml
中添加以下連個依賴項:redis
<!--開啓緩存-->
<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
文件,因此咱們須要在該目錄下手動建立該文件,這裏先給出一個樣例:spring
<?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>
複製代碼
下面介紹樣例中出現的三個節點:sql
<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
實現層使用緩存功能,經常使用的註解以下:
該註解主要用在方法上邊,每當程序進入被該註解標記的方法時,系統會首先判斷緩存中是否存在相同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){//內容省略...};
複製代碼
該註解主要用在方法上邊,可以根據方法的參數以及返回值以及自定義的條件判斷是否添加緩存,該註解標記的方法必定會執行,其屬性與@Cacheable
一致。
@CachePut(value="userCache",key="#entity.id")
public SysUser insertSysuser(SysUser entity) {
// TODO Auto-generated method stub
//省略內容
}
複製代碼
該註解主要用在方法上邊,能根據條件對緩存進行清空,經常使用屬性以下:
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~~