推荐文章阅读
Click me to read SpringBoot与缓存 ~ 概念、注解、整合Redis、分布式锁
Click me to read SpringBoot整合缓存 ~ SpringBoot缓存工作原理以及@Cacheable运行流程
Click me to read SpringBoot整合缓存 ~ 整合Redis缓存和序列化
前言
之 前 想 写 S p r i n g B o o t 高 级 篇 整 合 各 种 技 术 的 , 一 直 拖 着 没 写 , 现 在 给 大 家 补 上 抱 歉 \color{red}之前想写SpringBoot高级篇整合各种技术的,一直拖着没写,现在给大家补上抱歉 之前想写SpringBoot高级篇整合各种技术的,一直拖着没写,现在给大家补上抱歉
SpringBoot整合Redis的博客很多,大致套路都是
- 引入Pom
- 配置Redis
- 使用注解@RedisTemplate注解
然后redisTemplate.opsForValue(),redisTemplate.opsForList()等方法去操作
如 果 是 让 你 去 整 合 R e d i s , 按 照 这 些 文 章 操 作 时 没 有 任 何 问 题 。 \color{#f6941d}如果是让你去整合Redis,按照这些文章操作时没有任何问题。 如果是让你去整合Redis,按照这些文章操作时没有任何问题。
但 是 你 会 “ 知 其 然 而 不 知 所 以 然 " , 可 能 你 会 整 合 , 但 是 你 并 不 明 白 期 间 的 一 些 细 节 点 \color{#f6941d}但是你会“知其然而不知所以然", 可能你会整合,但是你并不明白期间的一些细节点 但是你会“知其然而不知所以然",可能你会整合,但是你并不明白期间的一些细节点
本篇主要从缓存概念、缓存注解、整合Redis三个方面讲,核心在于缓存注解,以及整合Redis和序列化机制。如果你是想学RedisTemplate Api, 建议可以绕到了,这里不会介绍详细使用API,更多的是一种知识普及。
缓存概念
作 为 过 来 人 , 很 多 人 在 学 习 时 , 更 多 倾 向 于 如 何 使 用 具 体 的 技 术 , 缺 忽 略 了 理 念 。 \color{red}作为过来人,很多人在学习时,更多倾向于如何使用具体的技术,缺忽略了理念。 作为过来人,很多人在学习时,更多倾向于如何使用具体的技术,缺忽略了理念。
要 知 道 一 句 话 : 天 上 飞 的 理 论 , 必 然 有 落 地 的 实 现 产 品 , 缓 存 是 理 念 , r e d i s 是 它 的 落 地 产 品 \color{red}要知道一句话:天上飞的理论,必然有落地的实现产品,缓存是理念,redis是它的落地产品 要知道一句话:天上飞的理论,必然有落地的实现产品,缓存是理念,redis是它的落地产品
落 地 产 品 并 不 只 有 这 一 款 , 十 几 甚 至 几 十 款 , 只 有 掌 握 这 个 理 论 , 才 能 举 一 反 三 , 更 好 的 理 解 落 地 产 品 \color{red}落地产品并不只有这一款,十几甚至几十款,只有掌握这个理论,才能举一反三,更好的理解落地产品 落地产品并不只有这一款,十几甚至几十款,只有掌握这个理论,才能举一反三,更好的理解落地产品
Java Caching ~ JSR107规范
Java Caching定义了5个核心接口,分别是CachingProvider,CachingManager、 Cache、 Entry 和 Expiry
- CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
- CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有
- Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有
- Entry是一个存储在Cache中的key-value对
- Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期
的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置
【举例】换成具体的实例理解
缓存注解
项目准备
SpringBoot整合mybatis-plus,过程就不演示了,直接上代码
@Cacheable 根据方法的请求参数对其结果进行缓存
若想使用缓存注解,必须开启缓存,在启动类上加上注解:@EnableCaching
案例1: 不使用缓存时,重复查询员工信息与加上缓存注解时重复查询员工信息
@Cacheable(cacheNames = "emp")
public Employee get(Integer id){
return employeeMapper.selectById(id);
}
@cachable 注解属性讲解
cacheNames/value:指定缓存组件的名字,支持多个名字,必须填写的。例如员工缓存组件、部门缓存组件,也可以同时指定
@Cacheable(cacheNames = {
"emp", "department"})
key: 指定缓存数据使用的Key,默认是使用方法参数的值,1-方法的返回值
支持SPEL: #id, 参数id的值 #a0 、#p0 、 #root.argsp[0]
案例2:若指定key为1,则第一次查库后,不管查询id为多少,都不查库,直接查缓存,说明就是用默认方法作为key
@Cacheable(cacheNames = {
"emp", "department"}, key = "1")
@Cacheable(cacheNames = {
"emp", "department"}, key = #id)
keyGenerator:指定缓存生成器,用于生成key,这个灵活性更强
【 注 意 】 k e y 和 k e y G e n e r a t o r 只 能 二 选 一 , 从 定 义 上 来 看 就 知 道 了 \color{red}【注意】key和keyGenerator只能二选一,从定义上来看就知道了 【注意】key和keyGenerator只能二选一,从定义上来看就知道了
案例3:
/**
* @author xiaozheng
* @version 1.0
* @date 2020/11/24 15:33
*/
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName() + "[" + Arrays.asList(params).toString() + "]";
}
};
}
}
@Cacheable(cacheNames = {
"emp", "department"}, keyGenerator = "myKeyGenerator")
public Employee get(Integer id){
return employeeMapper.selectById(id);
}
直接打断点就可以看到,生成key为随机数,发现都会查询数据库了
condition:支持SPEL表达式,当满足该条件的时候才缓存,否则不缓存
案例4:
@Cacheable(cacheNames = {
"emp", "department"}, keyGenerator = "myKeyGenerator",
condition = "#id > 1"
)
观察现象
unless: 否定缓存,当unless条件为true时,不缓存,若unless条件为false才缓存
案例5:
// 若查询的id大于1,且id小于5的,才会缓存
@Cacheable(cacheNames = {
"emp", "department"}, keyGenerator = "myKeyGenerator",
condition = "#id > 1", unless = "#id > 5"
)
sync:是否使用异步,这个不讲
以@Cacheable为例,看一下缓存的工作原理
Click me to study SpringBoot整合缓存 ~ SpringBoot缓存工作原理以及@Cacheable运行流程,强烈推荐哦
@CachePut 更新数据并且将结果缓存
案例1:
1、查询1号员工,将查询结果放入缓存
2、以后查询还是原来的结果
3、更新1号员工,也将结果缓存
4、重新查询1号员工,查询时之前的数据还是新的数据呢?
// controller
/**
* @author xiaozheng
* @version 1.0
* @date 2020/11/24 14:48
*/
/**
* @author xiaozheng
* @version 1.0
* @date 2020/11/24 14:48
*/
@RestController
@RequestMapping("/emp")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@RequestMapping("/get/{id}")
public Employee get(@PathVariable("id") Integer id){
return employeeService.get(id);
}
@RequestMapping("/update")
public Employee update(Integer id, String lastName, Integer geneder, String email){
Employee employee = new Employee();
employee.setLastName(lastName);
employee.setId(id);
employee.setEmail(email);
employee.setGender(geneder);
return employeeService.update(employee);
}
}
// service
/**
* @author xiaozheng
* @version 1.0
* @date 2020/11/24 14:50
*/
@Service
public class EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
// 若查询的id大于1,且id小于5的,才会缓存
@Cacheable(cacheNames = {
"emp"})
public Employee get(Integer id){
return employeeMapper.selectById(id);
}
@CachePut(cacheNames = {
"emp"})
public Employee update(Employee employee){
employeeMapper.updateById(employee);
return employee;
}
}
结 果 : 查 询 的 是 没 更 新 前 的 数 据 \color{red}结果: 查询的是没更新前的数据 结果:查询的是没更新前的数据
原因在于:key不一样
若指定:
@CachePut(value = {
"emp"}, key="#result.id")
http://localhost:8080/emp/get/1
http://localhost:8080/emp/update?id=1&lastName=2&geneder=150&email=1
若换种操作,将controller改为:
@RequestMapping("/update")
public Employee update(Integer id, String lastName, Integer geneder, String email){
Employee employee = employeeService.get(id);
employee.setLastName(lastName);
employee.setId(id);
employee.setEmail(email);
employee.setGender(geneder);
return employeeService.update(employee);
}
先去缓存找,然后再更新,它就会同步更新缓存,大家可以试试哦
@CacheEVict 请求缓存
@CacheEVict 注解属性讲解
value和cacheName指定定缓存组件
key:指定需要删除的key
allEntries: 请求该缓存组件中的所有缓存数据
beforeInvocation: 指定缓存的清除是否在方法之前,默认是false,若方法执行异常,则不会清除缓存,若设为true,不管方法执行结果如何,都清除缓存
案例1:测试效果
*/
@RestController
@RequestMapping("/emp")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@RequestMapping("/get/{id}")
public Employee get(@PathVariable("id") Integer id){
return employeeService.get(id);
}
@RequestMapping("/delete/{id}")
public Integer delete(@PathVariable("id") Integer id){
employeeService.delete(id);
return id;
}
@RequestMapping("/update")
public Employee update(Integer id, String lastName, Integer geneder, String email){
Employee employee = employeeService.get(id);
employee.setLastName(lastName);
employee.setId(id);
employee.setEmail(email);
employee.setGender(geneder);
return employeeService.update(employee);
}
}
/**
* @author xiaozheng
* @version 1.0
* @date 2020/11/24 14:50
*/
@Service
public class EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
// 若查询的id大于1,且id小于5的,才会缓存
@Cacheable(value = {
"emp"})
public Employee get(Integer id){
return employeeMapper.selectById(id);
}
@CachePut(value = {
"emp"}, key="#result.id")
public Employee update(Employee employee){
employeeMapper.updateById(employee);
return employee;
}
@CacheEvict(value = {
"emp"})
public void delete(Integer id){
System.out.println("删除s数据");
}
}
核心代码:
@CacheEvict(value = {
"emp"})
案例2:
@CacheEvict(value = {
"emp"}, allEntries = true)
案例3:
@CacheEvict(value = {
"emp"}, allEntries = true, beforeInvocation = true)
public void delete(Integer id){
System.out.println("删除s数据");
int i = 10 / 0 ;
}
@Caching 注解
若一个方法的缓存规则比价复杂时,可用该注解
// @Caching 定义复杂的缓存规则
@Caching(
cacheable = {
@Cacheable(/*value="emp",*/key = "#lastName")
},
put = {
@CachePut(/*value="emp",*/key = "#result.id"),
@CachePut(/*value="emp",*/key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}