概述
??Spring提供了對(duì)應(yīng)用程序添加緩存的支持。從本質(zhì)上講,將緩存應(yīng)用于方法上,從而根據(jù)緩存中的信息減少執(zhí)行次數(shù)。當(dāng)開發(fā)者調(diào)用一個(gè)方法時(shí),將方法的參數(shù)和返回值作為Key/Value緩存起來(lái),當(dāng)再次調(diào)用這個(gè)方法時(shí),如果緩存中存在對(duì)應(yīng)數(shù)據(jù),就從緩存中獲取數(shù)據(jù),否則再去執(zhí)行該方法。
支持
Spring并沒(méi)有提供緩存的實(shí)現(xiàn),而是提供了一套Api,可以自由選擇緩存的實(shí)現(xiàn)。目前Spring Boot支持的緩存有如下幾種(Spring Boot會(huì)按順序檢測(cè)以下提供的程序):
- Generic
- JCache(JSR-107)(EhCache 3, Hazelcast, Infinispan, and others)
- EhCache 2.x
- Hazelcast
- Infinispan
- Couchbase
- Readis
- Caffeine
- Simple
可以通過(guò)設(shè)置屬性來(lái)指定提供緩存的程序。
無(wú)論使用哪種緩存實(shí)現(xiàn)不同的只是緩存配置,使用的緩存注解是一致的
| 注解 | 介紹 |
|---|---|
| @EnableCaching | 啟用S??pring的注釋驅(qū)動(dòng)的緩存管理功能 |
| @CacheConfig | 當(dāng)此批注出現(xiàn)在給定的類上時(shí),它為該類中定義的任何緩存操作提供一組默認(rèn)設(shè)置。 |
| @Cacheable | 表示可以緩存調(diào)用方法(或類中的所有方法)結(jié)果的注釋。每次調(diào)用指定方法時(shí),將進(jìn)行緩存行為,檢查是否已緩存該方法參數(shù)和結(jié)果。如果在緩存中找不到鍵的值,則將調(diào)用目標(biāo)方法并將返回的值存儲(chǔ)在關(guān)聯(lián)的緩存中。 |
| @CacheEvict | 表示方法(或類上的所有方法)觸發(fā)緩存逐出操作的注解。 |
| @CachePut | 表示方法(或類上的所有方法)觸發(fā)緩存放置操作的注釋。 |
| @Caching | 此批注可用作元批注,以創(chuàng)建具自定義組合批注。 |
一、Ehcache 2.x 緩存
在項(xiàng)目的pom.xml文件中添加以下依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
添加緩存配置文件
如果存在Ehcache的依賴,并且在classpath下有名為ehcache.xml的文件,那么EhCacheCacheManager將會(huì)自動(dòng)作為緩存的提供者。因此,在resources目錄下創(chuàng)建ehcache.xml文件作為Ehcach緩存的配置文件。
如果未配置ehcache.xml,則Ehcache依賴包下的ehcache-failsafe.xml是ehcache的默認(rèn)配置。
<ehcache>
<!--diskStore元素是可選的。-->
<!--如果為任何緩存啟用了overflowToDisk或diskPersistent,則必須對(duì)其進(jìn)行配置。-->
<!--diskStore只有一個(gè)屬性 - path。這是將在其中創(chuàng)建.data和.index文件的目錄路徑。-->
<!--如果路徑是Java系統(tǒng)屬性,則將其替換為正在運(yùn)行的VM中的值-->
<!--user.home - 用戶的主目錄-->
<!--user.dir - 用戶的當(dāng)前工作目錄-->
<!--java.io.tmpdir - 默認(rèn)臨時(shí)文件路徑-->
<!--ehcache.disk.store.dir - 在命令行上指定的系統(tǒng)屬性(例:java -Dehcache.disk.store.dir=/u01/myapp/diskdir)-->
<!--子目錄可以在屬性后指定 例:java.io.tmpdir/cache-->
<diskStore path="java.io.tmpdir/cache"/>
<!--強(qiáng)制性默認(rèn)緩存配置-->
<!--這些設(shè)置將應(yīng)用于使用CacheManager.add(String cacheName)創(chuàng)建的緩存。-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<!--localTempSwap - 該策略允許緩存在緩存操作期間使用本地磁盤。磁盤存儲(chǔ)是臨時(shí)的,并在重新啟動(dòng)后清除。-->
<!--當(dāng)策略為"none"時(shí),所有緩存都保留在內(nèi)存中(磁盤無(wú)溢出,磁盤無(wú)持久性)。-->
<persistence strategy="localTempSwap"/>
</defaultCache>
<cache name="student"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"/>
</ehcache>
緩存配置 <cache/>:
| 屬性 | 介紹 |
|---|---|
| name | 設(shè)置緩存的名稱,用于標(biāo)識(shí)緩存。 |
| maxElementsInMemory | 設(shè)置在內(nèi)存中創(chuàng)建的最大對(duì)象數(shù)(0 ==無(wú)限制)。 |
| maxElementsOnDisk | 設(shè)置將在DiskStore中維護(hù)的最大對(duì)象數(shù)。默認(rèn)值為零,表示無(wú)限制。 |
| eternal | 設(shè)置元素是否一直存在。如果為true,超時(shí)將被忽略。 |
| overflowToDisk | 設(shè)置當(dāng)內(nèi)存中的緩存達(dá)到maxInMemory限制時(shí)元素是否可以溢出到磁盤。 |
| timeToIdleSeconds | 設(shè)置元素過(guò)期之前的空閑時(shí)間,即緩存創(chuàng)建以后最后一次訪問(wèn)緩存的時(shí)間到超時(shí)失效時(shí)的時(shí)間間隔。值為0表示無(wú)窮大,默認(rèn)值為0。 |
| timeToLiveSeconds | 設(shè)置元素過(guò)期之前的生存時(shí)間,即從創(chuàng)建時(shí)間到元素過(guò)期之間的間隔。值為0表示一直存在,默認(rèn)值為0。 |
| diskPersistent | 磁盤存儲(chǔ)在虛擬機(jī)重新啟動(dòng)后是否仍然存在,默認(rèn)值為false。 |
| diskExpiryThreadIntervalSeconds | 做元素失效監(jiān)測(cè)以及清除工作的線程運(yùn)行間隔時(shí)間,默認(rèn)值為120秒。 |
| diskSpoolBufferSizeMB | 磁盤緩沖區(qū)大小,默認(rèn)30MB(內(nèi)部以字節(jié)為單位,設(shè)置的值轉(zhuǎn)換為字節(jié)后不可超過(guò)正整數(shù)表示范圍)。 |
| memoryStoreEvictionPolicy | 達(dá)到maxElementsInMemory限制后,將強(qiáng)制執(zhí)行清除策略。默認(rèn)策略是最近最少使用(LRU),其他可用策略先進(jìn)先出(指定為FIFO)和不常用(指定為L(zhǎng)FU)。 |
開啟緩存
在項(xiàng)目的入口類上添加@EnableCaching注解開啟緩存。
@EnableCaching
@SpringBootApplication
public class EhcacheApplication {
public static void main(String[] args) {
SpringApplication.run(EhcacheApplication.class, args);
}
}
創(chuàng)建數(shù)據(jù)接口
@Service
@CacheConfig(cacheNames = "student")
public class StudentService {
@Resource
private StudentMapper studentMapper;
@Cacheable
public Student findById(Integer id) {
Student student = studentMapper.findById(id);
System.out.println("查詢 :" + JSON.toJSONString(student));
return student;
}
@CachePut(key = "#student.id")
public Student insert(Student student) throws Exception {
int status = studentMapper.insert(student);
if (status == 0) {
throw new Exception("Insert failed");
}
System.out.println("插入 :" + JSON.toJSONString(student));
return student;
}
@CacheEvict(key = "#student.id")
public Student delete(Student student) throws Exception {
int status = studentMapper.delete(student);
if (status == 0) {
throw new Exception("Delete failed");
}
System.out.println("刪除 :" + JSON.toJSONString(student));
return student;
}
@CachePut(key = "#student.id")
public Student update(Student student) throws Exception {
int status = studentMapper.update(student);
if (status == 0) {
throw new Exception("Update failed");
}
System.out.println("更新 :" + JSON.toJSONString(student));
return student;
}
}
創(chuàng)建請(qǐng)求接口
@RestController
@RequestMapping("/std")
public class StudentController {
@Resource
private StudentService studentService;
/**
* 查詢
*/
@RequestMapping("/sel")
public String findById(Student student) {
Map<String, Object> msg = new HashMap<>();
try {
student = studentService.findById(student.getId());
msg.put("status", 200);
msg.put("result", student);
} catch (Exception e) {
e.printStackTrace();
}
return JSON.toJSONString(msg);
}
/**
* 插入
*/
@RequestMapping("/ins")
public String insert(Student student) {
Map<String, Object> msg = new HashMap<>();
try {
studentService.insert(student);
msg.put("status", 200);
msg.put("message", "插入成功");
} catch (Exception e) {
msg.put("status", 400);
msg.put("message", "插入失敗");
}
return JSON.toJSONString(msg);
}
/**
* 刪除
*/
@RequestMapping("/del")
public String delete(Student student) {
Map<String, Object> msg = new HashMap<>();
try {
studentService.delete(student);
msg.put("status", 200);
msg.put("message", "刪除成功");
} catch (Exception e) {
msg.put("status", 400);
msg.put("message", "刪除失敗");
}
return JSON.toJSONString(msg);
}
/**
* 更新
*/
@RequestMapping("/upd")
public String update(Student student) {
Map<String, Object> msg = new HashMap<>();
try {
studentService.update(student);
msg.put("status", 200);
msg.put("message", "更新成功");
} catch (Exception e) {
msg.put("status", 400);
msg.put("message", "更新失敗");
}
return JSON.toJSONString(msg);
}
}
測(cè)試
向數(shù)據(jù)庫(kù)中添加數(shù)據(jù):

(一) 查詢
連續(xù)訪問(wèn)查詢接口頁(yè)面并觀察輸出信息:


根據(jù)輸出信息發(fā)現(xiàn),在瀏覽器中多次獲取數(shù)據(jù),數(shù)據(jù)查詢方法只執(zhí)行了一遍。
(二) 更新
訪問(wèn)更新接口:

再次訪問(wèn)查詢接口:

觀察控制臺(tái):

發(fā)現(xiàn)訪問(wèn)更新接口后再次訪問(wèn)查詢接口,查詢接口并沒(méi)有再次從數(shù)據(jù)庫(kù)獲取數(shù)據(jù),而是從緩存中獲取,所以更新接口返回的結(jié)果會(huì)覆蓋指定參數(shù)鍵的緩存。
(三) 插入
訪問(wèn)插入接口:

訪問(wèn)查詢接口:

觀察控制臺(tái):

發(fā)現(xiàn)插入數(shù)據(jù)后再次查詢并沒(méi)有從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù),而是從緩存中獲取。因?yàn)椴迦霑r(shí)對(duì)參數(shù)和返回的結(jié)果進(jìn)行了緩存。
(四) 刪除
訪問(wèn)刪除接口:

訪問(wèn)查詢接口:

觀察控制臺(tái):

發(fā)現(xiàn)訪問(wèn)刪除接口后數(shù)據(jù)庫(kù)中的數(shù)據(jù)被刪除,再次訪問(wèn)查詢接口進(jìn)行查詢發(fā)現(xiàn)觸發(fā)了查詢方法,說(shuō)明刪除數(shù)據(jù)時(shí)緩存同時(shí)也被刪除了。
二、Redis 單機(jī)緩存
和Ehcache一樣,如果在classpath下存在Redis并且Redis已經(jīng)配置好,此時(shí)會(huì)默認(rèn)使用RedisCacheManager作為緩存的提供者。
在項(xiàng)目的pom.xml文件中添加以下依賴(與之前的依賴相比不同的只是緩存的提供者):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
添加項(xiàng)目配置:
spring:
datasource:
url: jdbc:mysql://xxx.xxx.xxx.xxx/user
username: xxxx
password: xxxxxx
# 緩存配置
cache:
# 配置緩存名,多個(gè)緩存可使用逗號(hào)分隔(one,two)
cache-names: student
# 緩存存在時(shí)間
redis:
time-to-live: 120s
redis:
host: xxx.xxx.xxx.xxx
port: xxxx
之后的流程與之前一致就不在闡述了。