文章目录
十二、SpringBoot与缓存
1、概念
<1>、JSR107
Java Caching定义了五个核心接口,分别是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设置。
<2>、SpringBoot缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager接口来统一不同的缓存技术;
并支持使用JCache(JSR-107)注解简化我们开发;
Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
使用Spring缓存抽象时我们需要关注以下两点;
1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据
2、Cache案例
<1>、搭建适合的环境
<2>、在数据库中创建表
DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`departmentName` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`lastName` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`gender` int(2) DEFAULT NULL,
`d_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
<3>、创建俩个带有几个私有属性的类,自动生成get、set方法
Employee
import java.io.Serializable;
public class Employee implements Serializable {
private Integer id;
private String lastName;
private String email;
private Integer gender; //性别 1男 0女
private Integer dId;
public Employee() {
super();
}
public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {
super();
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.dId = dId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getdId() {
return dId;
}
public void setdId(Integer dId) {
this.dId = dId;
}
@Override
public String toString() {
return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + ", dId="
+ dId + "]";
}
}
Department
public class Department {
private Integer id;
private String departmentName;
public Department() {
super();
// TODO Auto-generated constructor stub
}
public Department(Integer id, String departmentName) {
super();
this.id = id;
this.departmentName = departmentName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
@Override
public String toString() {
return "Department [id=" + id + ", departmentName=" + departmentName + "]";
}
}
<4>、整合MyBatis
(1)、编写配置信息
spring.datasource.url=jdbc:mysql://localhost:3306/spring_cache
spring.datasource.username=root
spring.datasource.password=xxxxxx
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
(2)、使用注解版的MyBatis
@MapperScan标注在主类上,自动扫描mapper接口所在的包
@MapperScan("com.example.cache.mapper")
(3)、编写接口,写出方法对应的SQL语句
@Mapper
public interface EmployeeMapper {
@Select("select * from employee where id = #{id}")
Employee getEmpById(Integer id);
@Update("update employee set lastName=#{lastName}, email=#{email}, gender=#{gender}, d_id=#{dId} where id=#{id}")
void updateEmp(Employee employee);
@Delete("delete from employee where id=#{id}")
void deleteEmp(Integer id);
@Insert("insert into employee(lastName,email,gender, d_id) values(#{lastName},#{email},#{gender},#{dId})")
void insertUser(Employee employee);
@Select("select * from employee where lastName = #{lastName}")
Employee getEmpByLastName(String lastName);
}
(4)、测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class Springboot01CacheApplicationTests {
@Autowired
EmployeeMapper employeeMapper;
@Test
public void contextLoads() {
Employee emp = employeeMapper.getEmpById(1);
System.out.println(emp);
}
}
Service层
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
public Employee getEmp(Integer id){
System.out.println("查询" + id + "号员工");
Employee employee = employeeMapper.getEmpById(id);
return employee;
}
}
Controller层
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee getEmployee(@PathVariable("id") Integer id){
Employee employee = employeeService.getEmp(id);
return employee;
}
}
<5>、缓存体验
<1>、开启基于注解的缓存
<2>、标注缓存注解即可(Cacheable)
Cacheable:根据方法请求参数进行保存
CachePut:既调用方法,又更新数据,同步更新保存
CacheEvict:清理缓存
在application.properties开启debug时候会打印日志
logging.level.com.example.cache.mapper=debug
在需要缓存的地方标注上@Cacheable,可以将方法的运行结果进行保存,再用到相同结果的时候,直接在缓存中获取,不用再调用方法。
@Cacheable
public Employee getEmp(Integer id){
System.out.println("查询" + id + "号员工");
Employee employee = employeeMapper.getEmpById(id);
return employee;
}
Cacheable内部通常是以Map形式保存的
<3>、简单总结
Service层
/**
* 将方法的运行结果进行缓存,以后遇到相同的数据,直接从缓存中获取、不用在调用该方法
*
*
* CacheManager管理多个Cache组件的,对缓存的真正的CRUD操作在Cache组件中,每一个缓存都有自己唯一的名字
*
* 几个属性:
* cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存
* key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 1-方法的返回值
* 编写SpEL表达式 #id:参数id的值 #a0 #p0 #root.args[0]
* keyGenerator:可以的生成器、可以自己指定key的生成器组件id
* key/keyGenerator:二选一使用
* 以下俩个不能同时使用,只能使用一个
* cacheManager:指定缓存管理器
* cacheResolver:指定缓存解析器
*
* condition:指定符合条件的情况下才缓存
* condition = "#a0>1":第一个参数的值大于1的时候才会进行缓存
* unless:否定缓存,当unless指定的条件为true,方法的返回值就不会被缓存,可以获取到结果进行判断
* sync:是否使用异步模式
*
* 原理:
* 1、自动配置类:CacheAutoConfiguration
*
* 运行流程:
* 1、方法运行之前,先查询Cache(缓存组件),按照cacheNames指定的名字获取;(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建
* 2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
* key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
* SimpleKeyGenerator生成key的默认策略:
* 如果没有参数:key=new SimpleKey()
* 如果有一个参数:key=参数的值
* 如果有多个参数:key=new SimpleKey(params)
* 3、没有查到缓存就调用目标方法
* 4、将目标方法返回的结果、放进缓存中
*
* @Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存
* 如果没有就运行方法并将结果放入缓存;以后兹阿莱调用就可以直接使用缓存中的数据
*
* 核心:
* 1、使用CacheManager【CurrentMapCacheManager】按照名字得到Cache【CurrentMapCache】组件
* 2、key使用keyGenerator生成的,默认是SimpleKeyGenerator
*
*
* @param id
* @return
*/
@Cacheable(cacheNames = "emp", keyGenerator = "myKeyGenerator", condition = "#a0>1")
public Employee getEmp(Integer id){
System.out.println("查询" + id + "号员工");
Employee employee = employeeMapper.getEmpById(id);
return employee;
}
Controller层
@GetMapping("/emp/{id}")
public Employee getEmployee(@PathVariable("id") Integer id){
Employee employee = employeeService.getEmp(id);
return employee;
}
<4>、CachePut
即调用方法,又更新数据,同步更新缓存
Service层
/**
* @CachePut
* 既调用方法,又更新数据,同步更新缓存
* 修改数据库的某个数据,同时进行缓存
*
* 运行时机:
* 1、先调用方法
* 2、将目标结果缓存
*/
@CachePut(cacheNames = "emp", key = "#result.id")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp" + employee);
employeeMapper.updateEmp(employee);
return employee;
}
Controller层
@GetMapping("/emp")
public Employee update(Employee employee){
Employee employee1 = employeeService.updateEmp(employee);
return employee1;
}
<5>、CacheEvict
Service层
/**
*@CacheEvict:清理缓存
*
* key:指定要清除的数据
* allEntries = true:指定清除这个缓存中所有的数据
* beforeInvocation=false:缓存的清除是否在方法之前执行
* 默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存不会被清除
*
* 如果是true的话:代表清除缓存操作是在方法运行之前执行,无论是否出现异常、缓存都清除
*
*/
@CacheEvict(value = "emp", key = "#id", allEntries = true)
public void deleteEmp(Integer id){
System.out.println("deleteEmp" + id);
employeeMapper.deleteEmp(id);
}
Controller层
@GetMapping("/delemp")
public String deleteEmp(Integer id){
employeeService.deleteEmp(id);
return "success";
}
<6>、Caching
Caching可以帮助我们定制更复杂的缓存规则,像是一个集合标签,可以在里面使用上面提到的三种方法
@Select("select * from employee where lastName = #{lastName}")
Employee getEmpByLastName(String lastName);
Service层
//定义复杂的缓存规则
@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);
}
Controller层
@GetMapping("emp/lastName/{lastName}")
public Employee getEmpByLastName(@PathVariable("lastName") String lastName){
return employeeService.getEmpByLastName(lastName);
}
<7>、简单总结
上面所有都是从emp的缓存中提取数据,就是value=“emp”,这样写起来很麻烦,有一个其他的办法让所有标签固定从一个地方来获取到同一张表的缓存,直接在该类上加上@CacheConfig,进行集中配置,抽取缓存的公共配置
@CacheConfig(cacheNames = "emp", cacheResolver = "employeeCacheManager") //抽取缓存的公共配置