1. 程式人生 > >springboot快取開發實戰

springboot快取開發實戰

前言:

快取在開發中是一個必不可少的優化點,近期在公司的專案重構中,關於快取優化了很多點,比如在載入一些資料比較多的場景中,會大量使用快取機制提高介面響應速度,簡介提升使用者體驗。關於快取,很多人對它都是既愛又恨,愛它的是:它能大幅提升響應效率,恨的是它如果處理不好,沒有用好比如LRU這種策略,沒有及時更新資料庫的資料就會導致資料產生滯後,進而產生使用者的誤讀,或者疑惑。這是很嚴重的一個問題,比如我在公司和某家公司(國內的一線旅遊開發公司)的對接的時候,線上總是出現我們推送介面資料但是網站的資料產生滯後的現象,詢問對方的技術人員,告訴我們是快取的問題,只要刪除快取就沒事了,我只能無奈…所以如何處理好快取,對我們開發人員來說是一個很棘手的問題。不過關於這一切,springboot已經提供給我們很便捷的開發工具!本篇部落格就來探索springBoot的快取註解如何使用!

歡迎工作一到五年的Java工程師朋友們加入Java填坑之路:860113481

群內提供免費的Java架構學習資料(裡面有高可用、高併發、高效能及分散式、Jvm效能調優、Spring原始碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!

本篇部落格的目錄

一:springBoot開啟快取註解

二:常用快取註解

三:使用例項

四:總結

一:springBoot開啟註解

1.1:搭建springBoot環境

在idea中,搭建一個springboot是很簡單easy的。接下來我簡單說一下步驟:

File->new->projiect->Spring Initializer->next->named->web(選中)->Finish->new Window

1.2:開始快取

主要是@EnableCaching用於開啟快取註解的驅動,否則後面使用的快取都是無效的!

二:常用快取註解

2.1:@CacheConfig

這個註解的的主要作用就是全域性配置快取,比如配置快取的名字(cacheNames),只需要在類上配置一次,下面的方法就預設以全域性配置為主,不需要二次配置,節省了部分程式碼。

2.2:@Cacheable

這個註解是最重要的,主要實現的功能再進行一個讀操作的時候。就是先從快取中查詢,如果查詢不到,就會走資料庫的執行方法,這是快取的註解最重要的一個方法,基本上我們的所有快取實現都要依賴於它。它具有的屬性為cacheNames:快取名字,condtion:快取的條件,unless:不快取的條件。可以指定SPEL表示式來實現,也可以指定快取的key,快取的內部實現一般都是key,value形式,類似於一個Map(實際上cacheable的快取的底層實現就是concurrenHashMap),指定了key,那麼快取就會以key作為鍵,以方法的返回結果作為值進行對映。

2.3:@CacheEvict

這個註解主要是配合@Cacheable一起使用的,它的主要作用就是清除快取,當方法進行一些更新、刪除操作的時候,這個時候就要刪除快取。如果不刪除快取,就會出現讀取不到最新快取的情況,拿到的資料都是過期的。它可以指定快取的key和conditon,它有一個重要的屬性叫做allEntries預設是false,也可以指定為true,主要作用就是清除所有的快取,而不以指定的key為主。

2.3:@CachePut

這個註解它總是會把資料快取,而不會去每次做檢查它是否存在,相比之下它的使用場景就比較少,畢竟我們希望並不是每次都把所有的資料都給查出來,我們還是希望能找到快取的資料,直接返回,這樣能提升我們的軟體效率。

2.4:@cache

這個註解它是上面的註解的綜合體,包含上面的三個註解(cacheable、cachePut、CacheEvict),可以使用這一個註解來包含上面的所有的註解,看原始碼如下

上面的註解總結如下表格:

三:使用例項

3.1:建立資料庫

我們來新建一個表,含義為文章,下面的示例將會在這張表中進行操作,所使用的框架為SSM+springboot

CREATE TABLE Artile (

`id` int(11) NOT NULL AUTO_INCREMENT ,

`title` varchar(30) CHARACTER SET gbk COLLATE gbk_chinese_ci NULL DEFAULT NULL ,

`author` varchar(30) CHARACTER SET gbk COLLATE gbk_chinese_ci NULL DEFAULT NULL ,

`content` mediumtext CHARACTER SET gbk COLLATE gbk_chinese_ci NULL ,

`file_name` varchar(30) CHARACTER SET gbk COLLATE gbk_chinese_ci NULL DEFAULT NULL ,

`state` smallint(2) NULL DEFAULT 1 COMMENT '狀態' ,

PRIMARY KEY (`id`)

)

ENGINE=InnoDB

DEFAULT CHARACTER SET=gbk COLLATE=gbk_chinese_ci

AUTO_INCREMENT=11

ROW_FORMAT=COMPACT

;

3.2:Mapper層

主要就是對Article進行增刪改查的業務操作,對映到具體的xml的sql裡,然後用service去呼叫

public interface ArticleMapper {

/**

* 插入一篇文章

* @param title

* @param author

* @param content

* @param fileName

* @return

*/

public Integer addArticle(@Param("title") String title,@Param("author")String author,

@Param("content")String content,@Param("fileName")String fileName);

/**

* 根據id獲取文章

* @param id

* @return

*/

public Article getArticleById(@Param("id") Integer id);

/**

* 更新content

* @param content

*/

public Integer updateContentById(@Param("content")String content,@Param("id")Integer id);

/**

* 根據id刪除文章

* @param id

* @return

*/

public Integer removeArticleById(@Param("id")Integer id);

/**

* 獲得上一次插入的id

* @return

*/

public Integer getLastInertId();

}

3.3:service層

主要需要注意的是我們上述講述的快取註解都是基於service層(不能放在contoller和dao層),首先我們在類上配置一個CacheConfig,然後配置一個cacheNames,那麼下面的方法都是以這個快取名字作為預設值,他們的快取名字都是這個,不必進行額外的配置。當進行select查詢方法的時候,我們配置上@Cacheable,並指定key,這樣除了第一次之外,我們都會把結果快取起來,以後的結果都會把這個快取直接返回。而當進行更新資料(刪除或者更新操作)的時候,使用@CacheEvict來清除快取,防止呼叫@Cacheabel的時候沒有更新快取

@Service

@CacheConfig(cacheNames = "articleCache")

public class ArticleService {

private AtomicInteger count =new AtomicInteger(0);

@Autowired

private ArticleMapper articleMapper;

/**

* 增加一篇文章 每次就進行快取

* @return

*/

@CachePut

public Integer addArticle(Article article){

Integer result = articleMapper.addArticle(article.getTitle(), article.getAuthor(), article.getContent(), article.getFileName());

if (result>0) {

Integer lastInertId = articleMapper.getLastInertId();

System.out.println("--執行增加操作--id:" + lastInertId);

}

return result;

}

/**

* 獲取文章 以傳入的id為鍵,當state為0的時候不進行快取

* @param id 文章id

* @return

*/

@Cacheable(key = "#id",unless = "#result.state==0")

public Article getArticle(Integer id) {

try {

//模擬耗時操作

Thread.sleep(5000);

} catch (InterruptedException e) {

e.printStackTrace();

}

final Article artcile = articleMapper.getArticleById(id);

System.out.println("--執行資料庫查詢操作"+count.incrementAndGet()+"次"+"id:"+id);

return artcile;

}

/**

* 通過id更新內容 清除以id作為鍵的快取

*

* @param id

* @return

*/

@CacheEvict(key = "#id")

public Integer updateContentById(String contetnt, Integer id) {

Integer result = articleMapper.updateContentById(contetnt, id);

System.out.println("--執行更新操作id:--"+id);

return result;

}

/**

* 通過id移除文章

* @param id 清除以id作為鍵的快取

* @return

*/

@CacheEvict(key = "#id")

public Integer removeArticleById(Integer id){

final Integer result = articleMapper.removeArticleById(id);

System.out.println("執行刪除操作,id:"+id);

return result;

}

}

3.4:controller層

主要是接受客戶端的請求,我們配置了@RestController表示它是一個rest風格的應用程式,在收到add請求會增加一條資料,get請求會查詢一條資料,resh會更新一條資料,rem會刪除一條資料

@RestController

@ComponentScan(basePackages = {"com.wyq.controller", "com.wyq.service"})

@MapperScan(basePackages = {"com.wyq.dao"})

public class ArticleController {

@Autowired

private ArticleService articleService;

@Autowired

ArticleMapper articleMapper;

@PostMapping("/add")

public ResultVo addArticle(@RequestBody Article article) {

System.out.println(article.toString());

Integer result = articleService.addArticle(article);

if (result >= 0) {

return ResultVo.success(result);

}

return ResultVo.fail();

}

@GetMapping("/get")

public ResultVo getArticle(@RequestParam("id") Integer id) {

Long start = System.currentTimeMillis();

Article article = articleService.getArticle(id);

Long end = System.currentTimeMillis();

System.out.println("耗時:"+(end-start));

if (null != article)

return ResultVo.success(article);

return ResultVo.fail();

}

/**

* 更新一篇文章

*

* @param contetnt

* @param id

* @return

*/

@GetMapping("/resh")

public ResultVo update(@RequestParam("content") String contetnt, @RequestParam("id") Integer id) {

final Integer result = articleService.updateContentById(contetnt, id);

if (result > 0) {

return ResultVo.success(result);

} else {

return ResultVo.fail();

}

}

/**

* 刪除一篇文章

*

* @param id

* @return

*/

@GetMapping("/rem")

public ResultVo remove(@RequestParam("id") Integer id) {

final Integer result = articleService.removeArticleById(id);

if (result > 0) {

return ResultVo.success(result);

} else {

return ResultVo.fail();

}

}

}

3.5:測試

這裡使用postman模擬介面請求

3.5.1:首先我們來增加一篇文章:請求add介面:

後臺返回表示成功:

我看到後臺資料庫已經插入了資料,它的id是11

3.5.2:執行查詢操作

在查詢操作中,getArticle,我使用執行緒睡眠的方式,模擬了5秒的時間來處理耗時性業務,第一次請求肯定會查詢資料庫,理論上第二次請求,將會走快取,我們來測試一下:首先執行查詢操作

介面響應成功,再看一下後臺列印:表示執行了一次查詢操作,耗時5078秒

好,重點來了,我們再次請求介面看看會返回什麼?理論上,將不會走資料庫執行操作,並且耗時會大大減少:與上面的比對,這次沒有列印執行資料庫查詢操作,證明沒有走資料庫,並且耗時只有5ms,成功了!快取發揮作用,從5078秒減小到5秒!大大提升了響應速度,哈哈!

3.5.3:更新操作

當我們進行修改操作的時候,我們希望快取的資料被清空:看介面返回值成功了,再看資料庫

後臺控制檯列印:

--執行更新操作id:--11

趁熱打鐵,我們再次請求三次查詢介面,看看會返回什麼?每次都會返回這樣的結果,但是我的直觀感受就是第一次最慢,第二次、第三次返回都很快

再看看後臺列印了什麼?執行id為11的資料庫查詢操作,這是因為快取被清空了,所以它又走資料庫了(獲得最新資料),然後後面的查詢都會走快取!很明顯,實驗成功!

3.5.4:刪除操作

同理,在刪除操作中,執行了一次刪除,那麼快取也會被清空,查詢的時候會再次走資料庫,這裡就不給具體實驗效果了,如果需要的同學,可以把程式碼下載下來,自己測試一下就知道了。

四:總結

本篇部落格介紹了springBoot中快取的一些使用方法,如何在開發中使用快取?怎樣合理的使用都是值得我們學習的地方,快取能大大提升程式的響應速度,提升使用者體驗,不過它適用的場景也是讀多寫少的業務場景,如果資料頻繁修改,快取將會失去意義,每次還是執行的資料庫操作!如何使用好它,還有更高效的方式,比如使用redismemoryCache等專業元件,本篇部落格只是探討的spring的註解快取,相對來說比較簡單。希望起到拋磚引玉的作用,在以後部落格中,我將介紹redis如何搭建叢集來實現快取!

歡迎工作一到五年的Java工程師朋友們加入Java填坑之路:860113481

群內提供免費的Java架構學習資料(裡面有高可用、高併發、高效能及分散式、Jvm效能調優、Spring原始碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!