<meta charset="utf-8">
依賴
- 在
pom文件添加如下依賴
<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
-
在啟動類添加
@EnableCaching注解開啟注解驅(qū)動的緩存管理,如下
@Configuration
@EnableAutoConfiguration
@ComponentScan("org.hsweb.demo")
@MapperScan("org.hsweb.demo.dao")
@EnableCaching//開啟注解驅(qū)動的緩存管理
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
示例項目基本邏輯
-
為了方便理解,示例項目演示了一個簡單的對用戶數(shù)據(jù)的增刪改查,主要有兩方面:
- redis緩存注解與mybatis集成,主要邏輯位于
SimpleUserService - 僅使用redis緩存注解,主要邏輯位于
RedisOnlyServiceImpl
- redis緩存注解與mybatis集成,主要邏輯位于
示例java代碼結(jié)構(gòu)
.
├── Application.java //啟動類
├── controller
│ ├── RedisOnlyController.java
│ └── UserController.java
├── dao
│ └── UserDao.java //mapper接口(mybatis)
├── po
│ └── User.java // 用戶po類
└── service
├── impl
│ ├── RedisOnlyServiceImpl.java
│ └── SimpleUserService.java
├── RedisOnlyService.java
└── UserService.java
redis 注解使用入門
@Cacheable 注解簡單使用教程——用于查詢操作接口
@Cacheable注解的作用:緩存被調(diào)用方法的結(jié)果(返回值),已經(jīng)緩存就不再調(diào)用注解修飾的方法,適用于查詢接口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,在用這個key查詢redis中有無緩存到對應(yīng)的值
*
* 若無緩存,則執(zhí)行方法selectById,并把方法返回的值緩存到redis
*
* 若有緩存,則直接把redis緩存的值返回給用戶,不執(zhí)行方法
*/
@Cacheable(cacheNames="user", key="#id")
@Override
public User selectById(String id) {
//直接new一個給定id的用戶對象,來返回給用戶
return new User(id,"redisOnly","password");
}
}
-
可見這是一個簡單查詢用戶接口。它與典型接口只多了
@Cacheable注解- 為了方便演示,我直接返回一個用戶對象,而不是查詢數(shù)據(jù)
啟動程序,訪問
http://localhost:9999/redisOnly/user/9876接口,查詢一個id為9876的用戶,結(jié)果如圖:
image
可以看見返回用戶數(shù)據(jù)的同時,spring還把用戶數(shù)據(jù)緩存到redis中。
-
第二次訪問
http://localhost:9999/redisOnly/user/9876接口(查詢id為9876的用戶)時就會直接從redis中返回redis緩存的用戶數(shù)據(jù)- 而不再執(zhí)行
RedisOnlyServiceImpl的selectById()方法
- 而不再執(zhí)行
深入理解@Cacheable注解
@Cacheable注解的作用:緩存被調(diào)用方法的結(jié)果(返回值),已經(jīng)緩存就不再調(diào)用注解修飾的方法,適用于查詢接口-
以下面代碼的
@Cacheable(cacheNames="user", key="#id")為例說明-
cacheNames="user"用于把用戶數(shù)據(jù)存在同一個用戶空間user中,如圖image-
cacheNames="user"本質(zhì)是在redis緩存鍵值對中的key加個user:的前綴
-
-
key="#id"當(dāng)中的#id是按照spring表達(dá)式(詳細(xì)看官方教程)書寫的,這里意思是使用方法參數(shù)中的id參數(shù)生成緩存鍵值對中的key- 若不滿足于上面的key生成規(guī)則,可以通過實現(xiàn)
KeyGenerator接口自定義,詳細(xì)看
- 若不滿足于上面的key生成規(guī)則,可以通過實現(xiàn)
實質(zhì)上
cacheNames和key這連個屬性都是用于配置redis緩存鍵值對中的key這里僅解析個屬性的作用,下面在解析spring遇到這個注解后的邏輯
-
為了方便理解,列出示例代碼的邏輯
graph TB
A[查詢id為9876的用戶] -->|RedisOnlyController| B(調(diào)用RedisOnlyServiceImpl前被aop攔截)
B --> C{id為9876的用戶數(shù)據(jù)是否緩存到redis?}
C -->|是| D[從redis中獲取用戶數(shù)據(jù)并立刻返回]
C -->|否| E[執(zhí)行service類的selectById方法并緩存結(jié)果后返回]
- 上面邏輯都是通過aop封裝好的,對我們來說上面過程是透明的。詳細(xì)可以看源碼中
CacheAspectSupport類的第二個execute()方法(傳送門)
@CacheEvict 注解簡單使用教程——用于刪除操作接口
@CacheEvict注解的作用:刪除redis中對應(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ù)庫對應(yīng)用戶數(shù)據(jù)的操作
return true;
}
}
service中的
delete()是一個空方法,僅多了個@CacheEvict-
啟動程序,設(shè)置請求方法為
DELETE,用postman訪問http://localhost:9999/redisOnly/user/9876接口,即可刪除id為9876的用戶數(shù)據(jù)于redis的緩存- 注意:
@RequestMapping設(shè)置的請求方法為RequestMethod.DELETE
- 注意:
@CacheEvict(cacheNames="user", key="#id")注解中的兩個屬性類似上面說的@Cacheable(cacheNames="user", key="#id")原理很簡單,僅僅是把緩存數(shù)據(jù)刪除,無特別邏輯
-
若想刪除redis緩存的所有用戶數(shù)據(jù),可以把注解改成
@CacheEvict(cacheNames="user", allEntries=true)- 本質(zhì)是刪除redis數(shù)據(jù)庫的
user命名空間下的所有鍵值對
- 本質(zhì)是刪除redis數(shù)據(jù)庫的
@CachePut 注解簡單使用教程——用于刪除操作接口
-
@CachePut注解的作用同樣是緩存被調(diào)用方法的結(jié)果(返回值),當(dāng)與@Cacheable不一樣的是:-
@CachePut在值已經(jīng)被緩存的情況下仍然會執(zhí)行被@CachePut注解修飾的方法,而@Cacheable不會 -
@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());
// 必須把更新后的用戶數(shù)據(jù)返回,這樣才能把它緩存到redis中
return user;
}
}
注意: 由于
update(User user)方法的參數(shù)不再是id而是對象user,key="#id"中的SpEL(spring表達(dá)式)被改為#user.id-
啟動程序,設(shè)置請求方法為
DELETE,用postman訪問http://localhost:9999/redisOnly/user/9876接口5次,如下圖- 可以看到每次訪問接口時時
update()方法都會執(zhí)行一次(即,executeCout的值為5) - 注意:
@RequestMapping設(shè)置的請求方法為RequestMethod.DELETE
- 可以看到每次訪問接口時時
image
- 為了更好地對比,我把
@Cacheable注解修飾的查詢操作改成如下,同樣用postman訪問5次
@Cacheable(cacheNames="user", key="#id")
@Override
public User selectById(String id) {
return new User(id,"redisOnly" + executeCout.incrementAndGet(),"password");
}
- 測試結(jié)果是,
selectById()方法只執(zhí)行了一次,后面4次請求都是從redis緩存里取出用戶數(shù)據(jù)返回到客戶端
redis注解與mybatis一起使用
redis注解與mybatis一起使用的方式很簡單,就是在上面提到的各個方法內(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 注解的其他知識點
-
除了
cacheNames和key,其他注解屬性的作用-
keyGenerator:定義鍵值對中key生成的類,和key屬性的不能同時存在 -
sync:如果設(shè)置sync=true:- a. 如果緩存中沒有數(shù)據(jù),多個線程同時訪問這個方法,則只有一個方法會執(zhí)行到方法,其它方法需要等待;
- b. 如果緩存中已經(jīng)有數(shù)據(jù),則多個線程可以同時從緩存中獲取數(shù)據(jù);
注意:
sync不能解決緩存穿透的問題由于 redis 注解不支持超時時間,所有不存在緩存擊穿和雪崩的問題
-
condition: 在執(zhí)行方法后,如果condition的值為true,則緩存數(shù)據(jù);如果不滿足條件,僅執(zhí)行方法,不緩存結(jié)果 -
unless:在執(zhí)行方法后,判斷unless ,如果值為true,則不緩存數(shù)據(jù)
-
-
@CacheConfig: 類級別的注解:- 如果我們在此注解中定義
cacheNames,則此類中的所有方法上的cacheNames默認(rèn)都是此值。 - 當(dāng)然
@Cacheable也可以重定義cacheNames的值
- 如果我們在此注解中定義


