1.1.介紹三個注解@Cacheable、@CacheEvict、@CachePut
1.1.1@Cacheable
概述:
1)標記在類上:表示該類所有的方法都是支持緩存的。
2)標記在方法上:如果一個方法上添加了@Cacheable標記,Spring在每次執行前都會檢查Cache中是否存在相同key的緩存元素,如果存在就不再執行該方法,而是直接從緩存中獲取結果進行返回,否則才會執行並將返回結果存入指定的緩存中。
用法:
@Cacheable可以指定三個屬性,value、key和condition。
value必須指定,表示當前方法的返回值會被緩存在哪個Cache上,對應Cache的名稱。可以是一個Cache也可以是多個Cache,當需要指定多個Cache時其是一個數組。
適用場景:查詢接口
【示例】
@Cacheable("cache1")//Cache是發生在cache1上的
public User find(Integer id) {
return null;
}
@Cacheable(cacheNames="user", key="#id") // 使用方法參數中的id參數生成緩存鍵值對中的key
public User query(Integer id) {
return null;
}
key 自定義緩存的鍵,用來指定Spring緩存時對應的key的
@Cacheable(value="users", key="#user.id")
public User find(User user) {
return null;
}
思路:
graph TB
A[查詢id為1的用戶] -->|RedisOnlyController| B(調用RedisOnlyServiceImpl前被aop攔截)
B --> C{id為1的用戶數據是否緩存到redis?}
C -->|是| D[從redis中獲取用戶數據並立刻返回]
C -->|否| E[執行service類的selectById方法並緩存結果后返回]
1.1.2@CacheEvict
概述:
本質是刪除redis數據庫的user命名空間下的所有鍵值對.原理很簡單,僅僅是把緩存數據刪除,無特別邏輯.
若想刪除redis緩存的所有用戶數據,可以把注解改成@CacheEvict(cacheNames="user", allEntries=true)
用法:
緩存被調用方法的結果(返回值),刪除redis中對應的緩存
@CacheEvict(cacheNames="user", key="#id")
@Override
public boolean delete(String id) {
// 可以在這里添加刪除數據庫對應用戶數據的操作
return true;
}
適用場景:刪除接口
1.1.3@CachePut
概述:
@CachePut也可以聲明一個方法支持緩存功能。不同的是使用@CachePut標注的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。
用法:
@Service
public class RedisOnlyServiceImpl implements UserService {
// 記錄
private AtomicInteger executeCout = new AtomicInteger(0);
@CachePut(cacheNames="user", key="#user.id")
@Override
public User update(User user) {
// 每次方法執行executeCout
user.setUsername("redisOnly" + executeCout.incrementAndGet());
// 必須把更新后的用戶數據返回,這樣才能把它緩存到redis中
return user;
}
}
適用場景:更新操作,插入操作
2.如何搭建Redis緩存
整體結構布局
.
├── 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
pom引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置application.yml
注意:xxx.xx.xx.xx表示你需要的端口號
server:
port: 8080
spring:
datasource: #數據源配置
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://xxx.xx.xx.xx:3310/0812erp?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
username: root
password: root
druid:
max-active: 10
min-idle: 5
max-wait: 5000
initial-size: 5
validation-query: select 1
stat-view-servlet:
enabled: true
login-username: admin
login-password: admin
allow:
deny:
url-pattern: "/druid/*"
thymeleaf:
cache: false
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
#==================redis 配置開始============================
#注意,redis是上面spring下面的,別寫錯了,層級關系
redis:
host: xxx.xx.xx.xx
password: xxxxxx
port: 6390
jedis:
pool:
max-active: 20
max-idle: 8
min-idle: 0
max-wait: 2000
#==================redis 配置結束============================
#shiro的配置
shiro:
hash-algorithm-name: md5
hash-iterations: 2
login-url: /index.html
unauthorized-url: /unauthorized.html
anon-urls:
- /**
- /index.html*
- /login/login*
- /resources/**
logout-url: /login/logout*
authc-urls:
# - /**
#mybatisplus的配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*/*Mapper.xml
修改Application
@SpringBootApplication
@MapperScan(basePackages = {"com.sxt.system.mapper"}) // 掃描mapper
@EnableCaching // 開啟緩存
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
創建RedisOnlyController
@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);
}
}
創建RedisOnlyServiceImpl
查詢:
@Service
public class RedisOnlyServiceImpl implements UserService {
/**
* 先用id生成key,在用這個key查詢redis中有無緩存到對應的值
*
* 若無緩存,則執行方法selectById,並把方法返回的值緩存到redis
*
* 若有緩存,則直接把redis緩存的值返回給用戶,不執行方法
*/
@Cacheable(cacheNames="user", key="#id")
@Override
public User selectById(String id) {
//直接new一個給定id的用戶對象,來返回給用戶
return new User(id,"redisOnly","password");
}
測試結果:
第一次訪問http://localhost:9999/redisOnly/user/1
接口(查詢id為1
的用戶)時就會從redis中查找redis緩存的用戶數據,沒有,放入數據庫和緩存中
第二次訪問http://localhost:9999/redisOnly/user/1
接口(查詢id為1
的用戶)時就會直接從redis中返回redis緩存的用戶數據
而不再執行RedisOnlyServiceImpl
的selectById()
方法
修改RedisOnlyController
@RequestMapping("/redisOnly")
@RestController()
public class RedisOnlyController {
@RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public boolean delete(@PathVariable String id) {
return redisOnlyService.delete(id);
}
}
修改RedisOnlyServiceImpl
刪除:
@Service
public class RedisOnlyServiceImpl implements UserService {
@CacheEvict(cacheNames="user", key="#id")
@Override
public boolean delete(String id) {
// 可以在這里添加刪除數據庫對應用戶數據的操作
return true;
}
}
測試:
啟動程序,設置請求方法為DELETE
,用postman訪問http://localhost:9999/redisOnly/user/9876
接口,即可刪除id為9876
的用戶數據於redis的緩存和數據庫對應的數據
修改RedisOnlyController
@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;
}
}
修改RedisOnlyServiceImpl
修改:
@Service
public class RedisOnlyServiceImpl implements UserService {
// 記錄
private AtomicInteger executeCout = new AtomicInteger(0);
@CachePut(cacheNames="user", key="#user.id")
@Override
public User update(User user) {
// 每次方法執行executeCout
user.setUsername("redisOnly" + executeCout.incrementAndGet());
// 必須把更新后的用戶數據返回,這樣才能把它緩存到redis中
return user;
}
}
測試:
啟動程序,設置請求方法為DELETE
,用postman訪問http://localhost:9999/redisOnly/user/9876
接口5次
我們發現每次訪問接口時時update()
方法都會執行一次(即,executeCout
的值為5)
注意:mybatis搭建,springboot搭建都是一樣的原理,不懂怎么搭建可看后續文檔。
3.redis注解的其他知識點(多線程訪問)
除了cacheNames
和key
,其他注解屬性的作用
keyGenerator
:定義鍵值對中key生成的類,和key
屬性的不能同時存在
sync:如果設置sync=true:
- a. 如果緩存中沒有數據,多個線程同時訪問這個方法,則只有一個方法會執行到方法,其它方法需要等待;
- b. 如果緩存中已經有數據,則多個線程可以同時從緩存中獲取數據;
注意:
sync
不能解決緩存穿透的問題