網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
?
1.1 介紹
Spring Cache是一個(gè)框架,實(shí)現(xiàn)了基于注解的緩存功能,只需要簡(jiǎn)單地加一個(gè)注解,就能實(shí)現(xiàn)緩存功能,大大簡(jiǎn)化我們?cè)跇I(yè)務(wù)中操作緩存的代碼。
Spring Cache只是提供了一層抽象,底層可以切換不同的cache實(shí)現(xiàn)。具體就是通過(guò)CacheManager接口來(lái)統(tǒng)一不同的緩存技術(shù)。CacheManager是Spring提供的各種緩存技術(shù)抽象接口。
針對(duì)不同的緩存技術(shù)需要實(shí)現(xiàn)不同的CacheManager:
CacheManager | 描述 |
---|---|
EhCacheCacheManager | 使用EhCache作為緩存技術(shù) |
GuavaCacheManager | 使用Google的GuavaCache作為緩存技術(shù) |
RedisCacheManager | 使用Redis作為緩存技術(shù) |
1.2 注解
在SpringCache中提供了很多緩存操作的注解,常見(jiàn)的是以下的幾個(gè):
注解 | 說(shuō)明 |
---|---|
@EnableCaching | 開(kāi)啟緩存注解功能 |
@Cacheable | 在方法執(zhí)行前spring先查看緩存中是否有數(shù)據(jù),如果有數(shù)據(jù),則直接返回緩存數(shù)據(jù);若沒(méi)有數(shù)據(jù),調(diào)用方法并將方法返回值放到緩存中 |
@CachePut | 將方法的返回值放到緩存中 |
@CacheEvict | 將一條或多條數(shù)據(jù)從緩存中刪除 |
在spring boot項(xiàng)目中,使用緩存技術(shù)只需在項(xiàng)目中導(dǎo)入相關(guān)緩存技術(shù)的依賴包,并在啟動(dòng)類上使用@EnableCaching開(kāi)啟緩存支持即可。
例如,使用Redis作為緩存技術(shù),只需要導(dǎo)入Spring data Redis的maven坐標(biāo)即可。
1.3 入門(mén)程序
接下來(lái),我們將通過(guò)一個(gè)入門(mén)案例來(lái)演示一下SpringCache的常見(jiàn)用法。上面我們提到,SpringCache可以集成不同的緩存技術(shù),如Redis、Ehcache甚至我們可以使用Map來(lái)緩存數(shù)據(jù), 接下來(lái)我們?cè)谘菔镜臅r(shí)候,就先通過(guò)一個(gè)Map來(lái)緩存數(shù)據(jù),最后我們?cè)贀Q成Redis來(lái)緩存。
1.3.1 環(huán)境準(zhǔn)備
1). 數(shù)據(jù)庫(kù)準(zhǔn)備
將今天資料中的SQL腳本直接導(dǎo)入數(shù)據(jù)庫(kù)中。
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`address` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2). 導(dǎo)入基礎(chǔ)工程
基礎(chǔ)環(huán)境的代碼,在我們今天的資料中已經(jīng)準(zhǔn)備好了, 大家只需要將這個(gè)工程導(dǎo)入進(jìn)來(lái)就可以了。
package com.znz.entity;
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
private int age;
private String address;
}
package com.znz.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.itheima.entity.User;
import com.itheima.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private CacheManager cacheManager;
@Autowired
private UserService userService;
/**
* CachePut:將方法返回值放入緩存
* value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
* key:緩存的key
*/
@CachePut(value = "userCache",key = "#user.id")
@PostMapping
public User save(User user){
userService.save(user);
return user;
}
/**
* CacheEvict:清理指定緩存
* value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
* key:緩存的key
*/
@CacheEvict(value = "userCache",key = "#p0")
//@CacheEvict(value = "userCache",key = "#root.args[0]")
//@CacheEvict(value = "userCache",key = "#id")
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id){
userService.removeById(id);
}
//@CacheEvict(value = "userCache",key = "#p0.id")
//@CacheEvict(value = "userCache",key = "#user.id")
//@CacheEvict(value = "userCache",key = "#root.args[0].id")
@CacheEvict(value = "userCache",key = "#result.id")
@PutMapping
public User update(User user){
userService.updateById(user);
return user;
}
/**
* Cacheable:在方法執(zhí)行前spring先查看緩存中是否有數(shù)據(jù),如果有數(shù)據(jù),則直接返回緩存數(shù)據(jù);若沒(méi)有數(shù)據(jù),調(diào)用方法并將方法返回值放到緩存中
* value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
* key:緩存的key
* condition:條件,滿足條件時(shí)才緩存數(shù)據(jù)
* unless:滿足條件則不緩存
*/
@Cacheable(value = "userCache",key = "#id",unless = "#result == null")
@GetMapping("/{id}")
public User getById(@PathVariable Long id){
User user = userService.getById(id);
return user;
}
@Cacheable(value = "userCache",key = "#user.id + '_' + #user.name")
@GetMapping("/list")
public List<User> list(User user){
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(user.getId() != null,User::getId,user.getId());
queryWrapper.eq(user.getName() != null,User::getName,user.getName());
List<User> list = userService.list(queryWrapper);
return list;
}
}
package com.znz.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User>{
}
package?com.znz.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.entity.User;
public interface UserService extends IService<User> {
}
package com.znz.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.entity.User;
import com.itheima.mapper.UserMapper;
import com.itheima.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}
由于SpringCache的基本功能是Spring核心(spring-context)中提供的,所以目前我們進(jìn)行簡(jiǎn)單的SpringCache測(cè)試,是可以不用額外引入其他依賴的。
3). 注入CacheManager
我們可以在UserController注入一個(gè)CacheManager,在Debug時(shí),我們可以通過(guò)CacheManager跟蹤緩存中數(shù)據(jù)的變化。
而在上述的這幾個(gè)實(shí)現(xiàn)中,默認(rèn)使用的是 ConcurrentMapCacheManager。稍后我們可以通過(guò)斷點(diǎn)的形式跟蹤緩存數(shù)據(jù)的變化。
4). 引導(dǎo)類上加@EnableCaching
在引導(dǎo)類上加該注解,就代表當(dāng)前項(xiàng)目開(kāi)啟緩存注解功能。
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@Slf4j
@SpringBootApplication
@EnableCaching
public class CacheDemoApplication {
public static void main(String[] args) {
SpringApplication.run(CacheDemoApplication.class,args);
log.info("項(xiàng)目啟動(dòng)成功...");
}
}
1.3.2 @CachePut注解
@CachePut 說(shuō)明:
作用: 將方法返回值,放入緩存
value: 緩存的名稱, 每個(gè)緩存名稱下面可以有很多key
key: 緩存的key ?----------> 支持Spring的表達(dá)式語(yǔ)言SPEL語(yǔ)法
1). 在save方法上加注解@CachePut
當(dāng)前UserController的save方法是用來(lái)保存用戶信息的,我們希望在該用戶信息保存到數(shù)據(jù)庫(kù)的同時(shí),也往緩存中緩存一份數(shù)據(jù),我們可以在save方法上加上注解 @CachePut,用法如下:
?/**
?* CachePut:將方法返回值放入緩存
?* value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
?* key:緩存的key
?*/
?@CachePut(value?=?"userCache",?key?=?"#user.id")
?@PostMapping
?public?User?save(User?user){
??? ?userService.save(user);
??? ?return?user;
?}
key的寫(xiě)法如下:
#user.id : #user指的是方法形參的名稱, id指的是user的id屬性 , 也就是使用user的id屬性作為key ;
#user.name: #user指的是方法形參的名稱, name指的是user的name屬性 ,也就是使用user的name屬性作為key ;
#result.id : #result代表方法返回值,該表達(dá)式 代表以返回對(duì)象的id屬性作為key ;
#result.name : #result代表方法返回值,該表達(dá)式 代表以返回對(duì)象的name屬性作為key ;
2). 測(cè)試
啟動(dòng)服務(wù),通過(guò)postman請(qǐng)求訪問(wèn)UserController的方法, 然后通過(guò)斷點(diǎn)的形式跟蹤緩存數(shù)據(jù)。
第一次訪問(wèn)時(shí),緩存中的數(shù)據(jù)是空的,因?yàn)閟ave方法執(zhí)行完畢后才會(huì)緩存數(shù)據(jù)。
第二次訪問(wèn)時(shí),我們通過(guò)debug可以看到已經(jīng)有一條數(shù)據(jù)了,就是上次保存的數(shù)據(jù),已經(jīng)緩存了,緩存的key就是用戶的id。
注意: 上述的演示,最終的數(shù)據(jù),實(shí)際上是緩存在ConcurrentHashMap中,那么當(dāng)我們的服務(wù)器重啟之后,緩存中的數(shù)據(jù)就會(huì)丟失。我們后面使用了Redis來(lái)緩存就不存在這樣的問(wèn)題了。
1.3.3 @CacheEvict注解
@CacheEvict 說(shuō)明:
作用: 清理指定緩存
value: 緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
key: 緩存的key ?----------> 支持Spring的表達(dá)式語(yǔ)言SPEL語(yǔ)法
1). 在 delete 方法上加注解@CacheEvict
當(dāng)我們?cè)趧h除數(shù)據(jù)庫(kù)user表的數(shù)據(jù)的時(shí)候,我們需要?jiǎng)h除緩存中對(duì)應(yīng)的數(shù)據(jù),此時(shí)就可以使用@CacheEvict注解, 具體的使用方式如下:
?/**
?* CacheEvict:清理指定緩存
?* value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
?* key:緩存的key
?*/
?@CacheEvict(value?=?"userCache",key?=?"#p0") ?//#p0 代表第一個(gè)參數(shù)
?//@CacheEvict(value = "userCache",key = "#root.args[0]") //#root.args[0] 代表第一個(gè)參數(shù)
?//@CacheEvict(value = "userCache",key = "#id") //#id 代表變量名為id的參數(shù)
?@DeleteMapping("/{id}")
?public?void?delete(@PathVariable?Long?id){
??? ?userService.removeById(id);
?}
2). 測(cè)試
要測(cè)試緩存的刪除,我們先訪問(wèn)save方法4次,保存4條數(shù)據(jù)到數(shù)據(jù)庫(kù)的同時(shí),也保存到緩存中,最終我們可以通過(guò)debug看到緩存中的數(shù)據(jù)信息。然后我們?cè)谕ㄟ^(guò)postman訪問(wèn)delete方法, 如下:
刪除數(shù)據(jù)時(shí),通過(guò)debug我們可以看到已經(jīng)緩存的4條數(shù)據(jù):
當(dāng)執(zhí)行完delete操作之后,我們?cè)俅伪4嬉粭l數(shù)據(jù),在保存的時(shí)候debug查看一下刪除的ID值是否已經(jīng)被刪除。
3). 在 update 方法上加注解@CacheEvict
在更新數(shù)據(jù)之后,數(shù)據(jù)庫(kù)的數(shù)據(jù)已經(jīng)發(fā)生了變更,我們需要將緩存中對(duì)應(yīng)的數(shù)據(jù)刪除掉,避免出現(xiàn)數(shù)據(jù)庫(kù)數(shù)據(jù)與緩存數(shù)據(jù)不一致的情況。
?
//@CacheEvict(value = "userCache",key = "#p0.id") ? //第一個(gè)參數(shù)的id屬性
?//@CacheEvict(value = "userCache",key = "#user.id") //參數(shù)名為user參數(shù)的id屬性
?//@CacheEvict(value = "userCache",key = "#root.args[0].id") //第一個(gè)參數(shù)的id屬性
?@CacheEvict(value?=?"userCache",key?=?"#result.id") ? ? ? ??//返回值的id屬性
?@PutMapping
?public?User?update(User?user){
??? ?userService.updateById(user);
??? ?return?user;
?}
加上注解之后,我們可以重啟服務(wù),然后測(cè)試方式,基本和上述相同,先緩存數(shù)據(jù),然后再更新某一條數(shù)據(jù),通過(guò)debug的形式查詢緩存數(shù)據(jù)的情況。
1.3.4 @Cacheable注解
@Cacheable 說(shuō)明:
作用: 在方法執(zhí)行前,spring先查看緩存中是否有數(shù)據(jù),如果有數(shù)據(jù),則直接返回緩存數(shù)據(jù);若沒(méi)有數(shù)據(jù),調(diào)用方法并將方法返回值放到緩存中
value: 緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
key: 緩存的key ?----------> 支持Spring的表達(dá)式語(yǔ)言SPEL語(yǔ)法
1). 在getById上加注解@Cacheable
?/**
?* Cacheable:在方法執(zhí)行前spring先查看緩存中是否有數(shù)據(jù),如果有數(shù)據(jù),則直接返回緩存數(shù)據(jù);若沒(méi)有數(shù)據(jù),調(diào)用方法并將方法返回值放到緩存中
?* value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
?* key:緩存的key
?*/
?@Cacheable(value?=?"userCache",key?=?"#id")
?@GetMapping("/{id}")
?public?User?getById(@PathVariable?Long?id){
??? ?User?user?=?userService.getById(id);
??? ?return?user;
?}
2). 測(cè)試
我們可以重啟服務(wù),然后通過(guò)debug斷點(diǎn)跟蹤程序執(zhí)行。我們發(fā)現(xiàn),第一次訪問(wèn),會(huì)請(qǐng)求我們controller的方法,查詢數(shù)據(jù)庫(kù)。后面再查詢相同的id,就直接獲取到數(shù)據(jù)庫(kù),不用再查詢數(shù)據(jù)庫(kù)了,就說(shuō)明緩存生效了。
當(dāng)我們?cè)跍y(cè)試時(shí),查詢一個(gè)數(shù)據(jù)庫(kù)不存在的id值,第一次查詢緩存中沒(méi)有,也會(huì)查詢數(shù)據(jù)庫(kù)。而第二次再查詢時(shí),會(huì)發(fā)現(xiàn),不再查詢數(shù)據(jù)庫(kù)了,而是直接返回,那也就是說(shuō)如果根據(jù)ID沒(méi)有查詢到數(shù)據(jù),那么會(huì)自動(dòng)緩存一個(gè)null值。我們可以通過(guò)debug,驗(yàn)證一下:
我們能不能做到,當(dāng)查詢到的值不為null時(shí),再進(jìn)行緩存,如果為null,則不緩存呢? 答案是可以的。
3). 緩存非null值
在@Cacheable注解中,提供了兩個(gè)屬性分別為:condition, unless 。
condition : 表示滿足什么條件, 再進(jìn)行緩存 ;
unless : 表示滿足條件則不緩存 ; 與上述的condition是反向的 ;
具體實(shí)現(xiàn)方式如下:
?/**
??* Cacheable:在方法執(zhí)行前spring先查看緩存中是否有數(shù)據(jù),如果有數(shù)據(jù),則直接返回緩存數(shù)據(jù);若沒(méi)有數(shù)據(jù),調(diào)用方法并將方法返回值放到緩存中
??* value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
??* key:緩存的key
??* condition:條件,滿足條件時(shí)才緩存數(shù)據(jù)
??* unless:滿足條件則不緩存
??*/
?@Cacheable(value?=?"userCache",key?=?"#id",?unless?=?"#result == null")
?@GetMapping("/{id}")
?public?User?getById(@PathVariable?Long?id){
??? ?User?user?=?userService.getById(id);
??? ?return?user;
?}
注意:此處,我們使用的時(shí)候只能夠使用 unless, 因?yàn)樵赾ondition中,我們是無(wú)法獲取到結(jié)果 #result的。
4). 在list方法上加注解@Cacheable
在list方法中進(jìn)行查詢時(shí),有兩個(gè)查詢條件,如果傳遞了id,根據(jù)id查詢;如果傳遞了name, 根據(jù)name查詢,那么我們緩存的key在設(shè)計(jì)的時(shí)候,就需要既包含id,又包含name。具體的代碼實(shí)現(xiàn)如下:
?@Cacheable(value?=?"userCache",key?=?"#user.id + '_' + #user.name")
?@GetMapping("/list")
?public?List<User>?list(User?user){
??? ?LambdaQueryWrapper<User>?queryWrapper?=?new?LambdaQueryWrapper<>();
??? ?queryWrapper.eq(user.getId()?!=?null,User::getId,user.getId());
??? ?queryWrapper.eq(user.getName()?!=?null,User::getName,user.getName());
??? ?List<User>?list?=?userService.list(queryWrapper);
??? ?return?list;
?}
然后再次重啟服務(wù),進(jìn)行測(cè)試。
第一次查詢時(shí),需要查詢數(shù)據(jù)庫(kù),在后續(xù)的查詢中,就直接查詢了緩存,不再查詢數(shù)據(jù)庫(kù)了。
?
2 ?集成Redis
在使用上述默認(rèn)的ConcurrentHashMap做緩存時(shí),服務(wù)重啟之后,之前緩存的數(shù)據(jù)就全部丟失了,操作起來(lái)并不友好。在項(xiàng)目中使用,我們會(huì)選擇使用redis來(lái)做緩存,主要需要操作以下幾步:
1). pom.xml
?<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>
2). application.yml
?spring:
??redis:
??? host:?192.168.200.200
??? port:?6379
??? password:?root@123456
??? database:?0
??cache:
??? redis:
??? ? time-to-live:?1800000 ??#設(shè)置緩存過(guò)期時(shí)間,可選
3). 測(cè)試
重新啟動(dòng)項(xiàng)目,通過(guò)postman發(fā)送根據(jù)id查詢數(shù)據(jù)的請(qǐng)求,然后通過(guò)redis的圖形化界面工具,查看redis中是否可以正常的緩存數(shù)據(jù)。
以上是針對(duì)SpringCache通用緩存框架的應(yīng)用介紹,有了它以后,可以理方便的在我們的程序中使用緩存,而且可以無(wú)縫移植。
?
原文鏈接:https://blog.csdn.net/whirlwind526/article/details/124929476
相關(guān)推薦
- 2022-10-05 redis?哨兵集群搭建的實(shí)現(xiàn)_Redis
- 2022-05-22 C#單例模式與多線程用法介紹_C#教程
- 2022-07-18 SpringBoot之定時(shí)任務(wù)三種實(shí)現(xiàn)方法
- 2022-08-02 Python深拷貝淺拷貝圖文示例清晰整理_python
- 2022-10-19 Pandas中Series的創(chuàng)建及數(shù)據(jù)類型轉(zhuǎn)換_python
- 2022-11-15 如何將Android?Studio卸載干凈_Android
- 2023-04-26 C語(yǔ)言函數(shù)聲明以及函數(shù)原型超詳細(xì)講解示例_C 語(yǔ)言
- 2022-06-30 Oracle在PL/SQL中使用存儲(chǔ)過(guò)程_oracle
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支