<meta charset="utf-8">
依賴(lài)
- 在
pom
文件添加如下依賴(lài)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置
- 在
application.yml
配置文件添加如下配置
spring.cache.type: REDIS
# REDIS (RedisProperties)
spring.redis.database: 0
spring.redis.host: 127.0.0.2
spring.redis.password:
spring.redis.port: 6379
spring.redis.pool.max-idle: 8
spring.redis.pool.min-idle: 0
spring.redis.pool.max-active: 100
spring.redis.pool.max-wait: -1
-
在啟動(dòng)類(lèi)添加
@EnableCaching
注解開(kāi)啟注解驅(qū)動(dòng)的緩存管理秽五,如下
@Configuration
@EnableAutoConfiguration
@ComponentScan("org.hsweb.demo")
@MapperScan("org.hsweb.demo.dao")
@EnableCaching//開(kāi)啟注解驅(qū)動(dòng)的緩存管理
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
示例項(xiàng)目基本邏輯
-
為了方便理解明场,示例項(xiàng)目演示了一個(gè)簡(jiǎn)單的對(duì)用戶(hù)數(shù)據(jù)的增刪改查巧娱,主要有兩方面:
- redis緩存注解與mybatis集成宦搬,主要邏輯位于
SimpleUserService
- 僅使用redis緩存注解建炫,主要邏輯位于
RedisOnlyServiceImpl
- redis緩存注解與mybatis集成宦搬,主要邏輯位于
示例java代碼結(jié)構(gòu)
.
├── Application.java //啟動(dòng)類(lèi)
├── controller
│ ├── RedisOnlyController.java
│ └── UserController.java
├── dao
│ └── UserDao.java //mapper接口(mybatis)
├── po
│ └── User.java // 用戶(hù)po類(lèi)
└── service
├── impl
│ ├── RedisOnlyServiceImpl.java
│ └── SimpleUserService.java
├── RedisOnlyService.java
└── UserService.java
redis 注解使用入門(mén)
@Cacheable
注解簡(jiǎn)單使用教程——用于查詢(xún)操作接口
@Cacheable
注解的作用:緩存被調(diào)用方法的結(jié)果(返回值)元媚,已經(jīng)緩存就不再調(diào)用注解修飾的方法稚伍,適用于查詢(xún)接口controller
層和service
層的相關(guān)代碼如下
@RequestMapping("/redisOnly")
@RestController()
public class RedisOnlyController {
@Resource
RedisOnlyService redisOnlyService;
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
public User getById(@PathVariable("id") String id) {
return redisOnlyService.selectById(id);
}
}
@Service
public class RedisOnlyServiceImpl implements UserService {
/**
* 先用id生成key弯予,在用這個(gè)key查詢(xún)r(jià)edis中有無(wú)緩存到對(duì)應(yīng)的值
*
* 若無(wú)緩存,則執(zhí)行方法selectById个曙,并把方法返回的值緩存到redis
*
* 若有緩存锈嫩,則直接把redis緩存的值返回給用戶(hù),不執(zhí)行方法
*/
@Cacheable(cacheNames="user", key="#id")
@Override
public User selectById(String id) {
//直接new一個(gè)給定id的用戶(hù)對(duì)象垦搬,來(lái)返回給用戶(hù)
return new User(id,"redisOnly","password");
}
}
-
可見(jiàn)這是一個(gè)簡(jiǎn)單查詢(xún)用戶(hù)接口呼寸。它與典型接口只多了
@Cacheable
注解- 為了方便演示,我直接返回一個(gè)用戶(hù)對(duì)象悼沿,而不是查詢(xún)數(shù)據(jù)
啟動(dòng)程序等舔,訪(fǎng)問(wèn)
http://localhost:9999/redisOnly/user/9876
接口,查詢(xún)一個(gè)id為9876
的用戶(hù)糟趾,結(jié)果如圖:
image
可以看見(jiàn)返回用戶(hù)數(shù)據(jù)的同時(shí)慌植,spring還把用戶(hù)數(shù)據(jù)緩存到redis中。
-
第二次訪(fǎng)問(wèn)
http://localhost:9999/redisOnly/user/9876
接口(查詢(xún)id為9876
的用戶(hù))時(shí)就會(huì)直接從redis中返回redis緩存的用戶(hù)數(shù)據(jù)- 而不再執(zhí)行
RedisOnlyServiceImpl
的selectById()
方法
- 而不再執(zhí)行
深入理解@Cacheable
注解
@Cacheable
注解的作用:緩存被調(diào)用方法的結(jié)果(返回值)义郑,已經(jīng)緩存就不再調(diào)用注解修飾的方法蝶柿,適用于查詢(xún)接口-
以下面代碼的
@Cacheable(cacheNames="user", key="#id")
為例說(shuō)明-
cacheNames="user"
用于把用戶(hù)數(shù)據(jù)存在同一個(gè)用戶(hù)空間user
中,如圖image-
cacheNames="user"
本質(zhì)是在redis緩存鍵值對(duì)中的key
加個(gè)user:
的前綴
-
-
key="#id"
當(dāng)中的#id
是按照spring表達(dá)式(詳細(xì)看官方教程)書(shū)寫(xiě)的,這里意思是使用方法參數(shù)中的id
參數(shù)生成緩存鍵值對(duì)中的key
- 若不滿(mǎn)足于上面的key生成規(guī)則非驮,可以通過(guò)實(shí)現(xiàn)
KeyGenerator
接口自定義交汤,詳細(xì)看
- 若不滿(mǎn)足于上面的key生成規(guī)則非驮,可以通過(guò)實(shí)現(xiàn)
實(shí)質(zhì)上
cacheNames
和key
這連個(gè)屬性都是用于配置redis緩存鍵值對(duì)中的key
這里僅解析個(gè)屬性的作用,下面在解析spring遇到這個(gè)注解后的邏輯
-
為了方便理解劫笙,列出示例代碼的邏輯
graph TB
A[查詢(xún)id為9876的用戶(hù)] -->|RedisOnlyController| B(調(diào)用RedisOnlyServiceImpl前被aop攔截)
B --> C{id為9876的用戶(hù)數(shù)據(jù)是否緩存到redis?}
C -->|是| D[從redis中獲取用戶(hù)數(shù)據(jù)并立刻返回]
C -->|否| E[執(zhí)行service類(lèi)的selectById方法并緩存結(jié)果后返回]
- 上面邏輯都是通過(guò)aop封裝好的芙扎,對(duì)我們來(lái)說(shuō)上面過(guò)程是透明的。詳細(xì)可以看源碼中
CacheAspectSupport
類(lèi)的第二個(gè)execute()
方法(傳送門(mén))
@CacheEvict
注解簡(jiǎn)單使用教程——用于刪除操作接口
@CacheEvict
注解的作用:刪除redis中對(duì)應(yīng)的緩存填大,適用于刪除接口controller
層和service
層的相關(guān)代碼如下
@RequestMapping("/redisOnly")
@RestController()
public class RedisOnlyController {
@RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public boolean delete(@PathVariable String id) {
return redisOnlyService.delete(id);
}
}
@Service
public class RedisOnlyServiceImpl implements UserService {
@CacheEvict(cacheNames="user", key="#id")
@Override
public boolean delete(String id) {
// 可以在這里添加刪除數(shù)據(jù)庫(kù)對(duì)應(yīng)用戶(hù)數(shù)據(jù)的操作
return true;
}
}
service中的
delete()
是一個(gè)空方法戒洼,僅多了個(gè)@CacheEvict
-
啟動(dòng)程序,設(shè)置請(qǐng)求方法為
DELETE
允华,用postman訪(fǎng)問(wèn)http://localhost:9999/redisOnly/user/9876
接口圈浇,即可刪除id為9876
的用戶(hù)數(shù)據(jù)于redis的緩存- 注意:
@RequestMapping
設(shè)置的請(qǐng)求方法為RequestMethod.DELETE
- 注意:
@CacheEvict(cacheNames="user", key="#id")
注解中的兩個(gè)屬性類(lèi)似上面說(shuō)的@Cacheable(cacheNames="user", key="#id")
原理很簡(jiǎn)單寥掐,僅僅是把緩存數(shù)據(jù)刪除,無(wú)特別邏輯
-
若想刪除redis緩存的所有用戶(hù)數(shù)據(jù)磷蜀,可以把注解改成
@CacheEvict(cacheNames="user", allEntries=true)
- 本質(zhì)是刪除redis數(shù)據(jù)庫(kù)的
user
命名空間下的所有鍵值對(duì)
- 本質(zhì)是刪除redis數(shù)據(jù)庫(kù)的
@CachePut
注解簡(jiǎn)單使用教程——用于刪除操作接口
-
@CachePut
注解的作用同樣是緩存被調(diào)用方法的結(jié)果(返回值)召耘,當(dāng)與@Cacheable
不一樣的是:-
@CachePut
在值已經(jīng)被緩存的情況下仍然會(huì)執(zhí)行被@CachePut
注解修飾的方法,而@Cacheable
不會(huì) -
@CachePut
注解適用于更新操作和插入操作
-
我們從更新操作(
update
方法)了解@CachePut
注解controller
層和service
層的相關(guān)代碼如下
@RequestMapping("/redisOnly")
@RestController()
public class RedisOnlyController {
@Resource
RedisOnlyService redisOnlyService;
@RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
public User update(@PathVariable String id, @RequestBody User user) {
user.setId(id);
redisOnlyService.update(user);
return user;
}
}
@Service
public class RedisOnlyServiceImpl implements UserService {
// 記錄
private AtomicInteger executeCout = new AtomicInteger(0);
@CachePut(cacheNames="user", key="#user.id")
@Override
public User update(User user) {
// 每次方法執(zhí)行executeCout
user.setUsername("redisOnly" + executeCout.incrementAndGet());
// 必須把更新后的用戶(hù)數(shù)據(jù)返回褐隆,這樣才能把它緩存到redis中
return user;
}
}
注意: 由于
update(User user)
方法的參數(shù)不再是id而是對(duì)象user污它,key="#id"
中的SpEL(spring表達(dá)式)被改為#user.id
-
啟動(dòng)程序,設(shè)置請(qǐng)求方法為
DELETE
庶弃,用postman訪(fǎng)問(wèn)http://localhost:9999/redisOnly/user/9876
接口5次轨蛤,如下圖- 可以看到每次訪(fǎng)問(wèn)接口時(shí)時(shí)
update()
方法都會(huì)執(zhí)行一次(即,executeCout
的值為5) - 注意:
@RequestMapping
設(shè)置的請(qǐng)求方法為RequestMethod.DELETE
- 可以看到每次訪(fǎng)問(wèn)接口時(shí)時(shí)
image
- 為了更好地對(duì)比虫埂,我把
@Cacheable
注解修飾的查詢(xún)操作改成如下,同樣用postman訪(fǎng)問(wèn)5次
@Cacheable(cacheNames="user", key="#id")
@Override
public User selectById(String id) {
return new User(id,"redisOnly" + executeCout.incrementAndGet(),"password");
}
- 測(cè)試結(jié)果是圃验,
selectById()
方法只執(zhí)行了一次掉伏,后面4次請(qǐng)求都是從redis緩存里取出用戶(hù)數(shù)據(jù)返回到客戶(hù)端
redis注解與mybatis一起使用
redis注解與mybatis一起使用的方式很簡(jiǎn)單,就是在上面提到的各個(gè)方法內(nèi)添加相應(yīng)的dao操作就行了
具體的service層代碼如下:
@Service("userService")
public class SimpleUserService implements UserService {
@Resource
private UserDao userDao;
@Cacheable(cacheNames="user", key="#id")
@Override
public User selectById(String id) {
return userDao.selectById(id);
}
@Cacheable(cacheNames="user", key="#username")
@Override
public User selectByUserName(String username) {
return userDao.selectByUserName(username);
}
@Override
public List<User> selectAll() {
return userDao.selectAll();
}
@CachePut(cacheNames="user", key="#user.id")
@Override
public User insert(User user) {
user.setId(UUID.randomUUID().toString());
userDao.insert(user);
return user;
}
@CachePut(cacheNames="user", key="#user.id")
@Override
public User update(User user) {
userDao.update(user);
return user;
}
@CacheEvict(cacheNames="user", key="#id")
@Override
public boolean delete(String id) {
return userDao.deleteById(id) == 1;
}
}
redis 注解的其他知識(shí)點(diǎn)
-
除了
cacheNames
和key
澳窑,其他注解屬性的作用-
keyGenerator
:定義鍵值對(duì)中key生成的類(lèi)斧散,和key
屬性的不能同時(shí)存在 -
sync
:如果設(shè)置sync=true:- a. 如果緩存中沒(méi)有數(shù)據(jù),多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)這個(gè)方法摊聋,則只有一個(gè)方法會(huì)執(zhí)行到方法鸡捐,其它方法需要等待;
- b. 如果緩存中已經(jīng)有數(shù)據(jù),則多個(gè)線(xiàn)程可以同時(shí)從緩存中獲取數(shù)據(jù);
注意:
sync
不能解決緩存穿透的問(wèn)題由于 redis 注解不支持超時(shí)時(shí)間麻裁,所有不存在緩存擊穿和雪崩的問(wèn)題
-
condition
: 在執(zhí)行方法后箍镜,如果condition的值為true,則緩存數(shù)據(jù)煎源;如果不滿(mǎn)足條件色迂,僅執(zhí)行方法,不緩存結(jié)果 -
unless
:在執(zhí)行方法后手销,判斷unless 歇僧,如果值為true,則不緩存數(shù)據(jù)
-
-
@CacheConfig
: 類(lèi)級(jí)別的注解:- 如果我們?cè)诖俗⒔庵卸x
cacheNames
锋拖,則此類(lèi)中的所有方法上的cacheNames
默認(rèn)都是此值诈悍。 - 當(dāng)然
@Cacheable
也可以重定義cacheNames
的值
- 如果我們?cè)诖俗⒔庵卸x