SpringBoot与缓存 ~ 概念、注解、整合Redis、分布式锁

126 阅读5分钟

推荐文章阅读

Click me to read SpringBoot与缓存 ~ 概念、注解、整合Redis、分布式锁

Click me to read SpringBoot整合缓存 ~ SpringBoot缓存工作原理以及@Cacheable运行流程

Click me to read SpringBoot整合缓存 ~ 整合Redis缓存和序列化

Click me to download

前言

之前想写SpringBoot高级篇整合各种技术的,一直拖着没写,现在给大家补上抱歉\color{red}之前想写SpringBoot高级篇整合各种技术的,一直拖着没写,现在给大家补上抱歉

SpringBoot整合Redis的博客很多,大致套路都是

  1. 引入Pom
  2. 配置Redis
  3. 使用注解@RedisTemplate注解 然后redisTemplate.opsForValue(),redisTemplate.opsForList()等方法去操作

如果是让你去整合Redis,按照这些文章操作时没有任何问题。\color{#f6941d}如果是让你去整合Redis,按照这些文章操作时没有任何问题。 但是你会“知其然而不知所以然",可能你会整合,但是你并不明白期间的一些细节点\color{#f6941d}但是你会“知其然而不知所以然", 可能你会整合,但是你并不明白期间的一些细节点 本篇主要从缓存概念、缓存注解、整合Redis三个方面讲,核心在于缓存注解,以及整合Redis和序列化机制。如果你是想学RedisTemplate Api, 建议可以绕到了,这里不会介绍详细使用API,更多的是一种知识普及。

缓存概念

作为过来人,很多人在学习时,更多倾向于如何使用具体的技术,缺忽略了理念。\color{red}作为过来人,很多人在学习时,更多倾向于如何使用具体的技术,缺忽略了理念。 要知道一句话:天上飞的理论,必然有落地的实现产品,缓存是理念,redis是它的落地产品\color{red}要知道一句话:天上飞的理论,必然有落地的实现产品,缓存是理念,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,这个灵活性更强 【注意】keykeyGenerator只能二选一,从定义上来看就知道了\color{red}【注意】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);
    }