前言:緩存在開發中是一個必不可少的優化點,近期在公司的項目重構中,關於緩存優化了不少點,好比在加載一些數據比較多的場景中,會大量使用緩存機制提升接口響應速度,簡介提高用戶體驗。關於緩存,不少人對它都是既愛又恨,愛它的是:它能大幅提高響應效率,恨的是它若是處理很差,沒有用比如如LRU這種策略,沒有及時更新數據庫的數據就會致使數據產生滯後,進而產生用戶的誤讀,或者疑惑。這是很嚴重的一個問題,好比我在公司和某家公司(國內的一線旅遊開發公司)的對接的時候,線上老是出現咱們推送接口數據可是網站的數據產生滯後的現象,詢問對方的技術人員,告訴咱們是緩存的問題,只要刪除緩存就沒事了,我只能無奈...因此如何處理好緩存,對咱們開發人員來講是一個很棘手的問題。不過關於這一切,springboot已經提供給咱們很便捷的開發工具!本篇博客就來探索springBoot的緩存註解如何使用!web
本篇博客的目錄redis
一:springBoot開啓緩存註解spring
二:經常使用緩存註解sql
三:使用實例數據庫
四:總結緩存
一:springBoot開啓註解springboot
1.1:搭建springBoot環境app
在idea中,搭建一個springboot是很簡單easy的。接下來我簡單說一下步驟:框架
File->new->projiect->Spring Initializer->next->named->web(選中)->Finish->new Windowless
1.2:開始緩存
@SpringBootApplication @EnableAutoConfiguration @EnableCaching public class SpringbootcacheApplication { public static void main(String[] args) { SpringApplication.run(SpringbootcacheApplication.class, args); } }
主要是@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中緩存的一些使用方法,如何在開發中使用緩存?怎樣合理的使用都是值得咱們學習的地方,緩存能大大提高程序的響應速度,提高用戶體驗,不過它適用的場景也是讀多寫少的業務場景,若是數據頻繁修改,緩存將會失去意義,每次仍是執行的數據庫操做!如何使用好它,還有更高效的方式,好比使用redis\memoryCache等專業組件,本篇博客只是探討的spring的註解緩存,相對來講比較簡單。但願起到拋磚引玉的做用,在之後博客中,我將介紹redis如何搭建集羣來實現緩存!
本篇博客的代碼示例下載地址(適用於intel idea):連接:https://pan.baidu.com/s/1CkRCFTlzfbKyg1R15Er6tw 密碼:nda4(若是文件失效請及時聯繫我,補鏈)