1.简单的整合项目
SpringBoot+Spring web+mybatis+cache+mysql
1)application.properties
#连接Mysql
spring.datasource.url=jdbc:mysql://localhost:3306/s003?characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#开启驼峰命名法
mybatis.configuration.map-underscore-to-camel-case=true
2)bean实例
Employee :
package cronos.study_g01_cacher.bean;
/**
* @ClassName: Employee
* @Author: cronos
* @Version: 1.0
**/
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;
private Integer dId;
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", gender=" + gender +
", 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;
}
}
3)创建mapper接口映射数据库
package cronos.study_g01_cacher.mapper;
import cronos.study_g01_cacher.bean.Employee;
import org.apache.ibatis.annotations.*;
/**
* @ClassName: EmployeeMapper
* @Author: cronos
* @Version: 1.0
**/
@Mapper
public interface EmployeeMapper {
//根据员工id查询员工信息
@Select("select * from employee where id = #{id}")
public Employee getEmpById(Integer id);
//更新员工信息
@Update("update employee set lastName = #{lastName},email = #{email},gender = #{gender},d_id = #{dId} where id = #{id}")
public void updateEmp(Employee employee);
//删除员工信息
@Delete("delete from employee where id = #{id}")
public void delEmp(Integer id);
//添加员工信息
@Insert("insert into employee(lastName,email,gender,d_id) values (#{lastName},#{email},#{gender},#{dId})")
public void addEmp(Employee employee);
//根据员工名查询员工信息
@Select("select * from employee where lastName = #{lastName}")
public Employee getEmpByLastName(String lastName);
}
4)主程序配置
package cronos.study_g01_cacher;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
/**
* @MapperScan指定需要扫描的mapper接口所在包
* @EnableCaching开启基于注解缓存
*/
@MapperScan("cronos.study_g01_cacher.mapper")
@EnableCaching
@SpringBootApplication
public class StudyG01CacherApplication {
public static void main(String[] args) {
SpringApplication.run(StudyG01CacherApplication.class, args);
}
}
5)创建service 实现mapper方法
package cronos.study_g01_cacher.service;
import cronos.study_g01_cacher.bean.Employee;
import cronos.study_g01_cacher.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
/**
* @ClassName: EmployeeService
* @Author: cronos
* @Version: 1.0
**/
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
@Cacheable(cacheNames = {"emp"})
public Employee getEmpById(Integer id){
System.out.println("查询"+id+"员工信息");
Employee employee = employeeMapper.getEmpById(id);
return employee;
}
@CachePut(value = "emp",key = "#result.id")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
@CacheEvict(value = "emp",key = "#id")
public void delEmp(Integer id){
//偷懒:输出即代表删除一条数据
System.out.println("delEmp"+id+"员工");
}
/**
* 指定多个缓存规则
*/
@Caching(
cacheable = {
@Cacheable(value = "emp",key="#lastName")
},
put = {
@CachePut(value = "emp",key = "#result.id"),
@CachePut(value = "emp",key = "#result.gender")
//@CachePut的存在会让如下方法函数一定执行的
}
)
public Employee getEmpByLastName(String lastName){
System.out.println("getEmpByLastName");
return employeeMapper.getEmpByLastName(lastName);
}
}
6)controller测试
package cronos.study_g01_cacher.controller;
import cronos.study_g01_cacher.bean.Employee;
import cronos.study_g01_cacher.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName: EmployeeController
* @Author: cronos
* @Version: 1.0
**/
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
//根据id查询
@GetMapping("/emp/{id}")
public Employee getEmpById(@PathVariable("id") Integer id){
Employee employee = employeeService.getEmpById(id);
return employee;
}
//更新员工
@GetMapping("emp")
public Employee updateEmp(Employee employee){
employeeService.updateEmp(employee);
return employee;
}
//删除
@GetMapping("/delEmp")
public String delEmp(Integer id){
employeeService.delEmp(id);
return "del success";
}
//根据名字查询
@GetMapping("/emp/lastName/{lastName}")
public Employee getEmpByLastName(@PathVariable("lastName") String lastName){
return employeeService.getEmpByLastName(lastName);
}
}
7)目录结构
2.缓存原理
* CacheAutoConfiguration
* 缓存原理:
* 1.自动配置类CacheAutoConfiguration
* 导入缓存组件@Import({ CacheConfigurationImportSelector.class})
* 2.[在application.properties中添加debug=true:可查看缓存配置生效情况]
* 查看缓存配置生效情况可知晓在自动配置导入组件时,只有SimpleCacheConfiguration生效:
* SimpleCacheConfiguration给容器注册了一个CacheManager:实际是ConcurrentMapCacheManager
* CacheManager可以获取和创建ConcurrentMapCache,作用是可将数据保存在ConcurrentMap中
*
* 运行流程:
* 1.方法运行之前,先查Cache(缓存组件),按照cacheName的指定名字获取;
* (CacheManager先获取相应的缓存),首次获取缓存如果没有cache组件会自己创建
* 2.去Cache中查找缓存的内容,使用一个key,默认为方法参数;
* key是按照某种策略生成的,默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key
* SimpleKeyGenerator生成key策略:
* 无参数 key=new SimpleKey();
* 若有一个参数 key=参数值
* 若多个参数 key=new SimpleKey(params);
* 3、没有查到缓存就调用目标方法
* 4、将目标方法返回的结果,放回缓存中
*
* 方法执行之前,@Cacheable先来检查缓存中是否有数据,默认按照参数的值作为key去查询缓存:
* 若有,则取出map的值。
* 若没有,则运行方法,存入缓存.
*
* 核心:
* 1.使用CacheManager:ConcurrentMapCacheManager按照名字得到Cache:ConcurrentMapCache
* 2.key使用keyGenerator生成,默认使用SimpleKeyGenerator生成
3.各注解的使用
-1- @Cacheable
* 将方法的运行结果进行缓存,以后要是再有相同的数据,直接从缓存中获取,不用调用方法
* CacheManager中管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每个缓存组件都有自己的唯一名字
* @Cacheable:
* 属性:
* 1.CacheName/value:指定存储缓存组件的名字
* 2.key:缓存数据使用的key,可以使用它来指定。默认是使用方法参数的值,1-方法的返回值
* 编写Spel表达式:#id 参数id的值, #a0/#p0 #root.args[0]
* 3.keyGenerator:key的生成器,可以自己指定key的生成器的组件id
* 【key/keyGendertor二选一】
*
* 4.cacheManager指定Cache管理器,或者cacheReslover指定获取解析器
* 5.condition:指定符合条件的情况下,才缓存;
* eg:condition = "#id>0"
* 6.unless(除非):否定缓存,unless指定的条件为true,方法的返回值就不会被缓存,可以获取到结果进行判断
* eg:unless = "#result==null"
* 7.sync:是否使用异步模式,unless不支持
//Service
@Cacheable(cacheNames = {"emp"})
public Employee getEmpById(Integer id){
System.out.println("查询"+id+"员工信息");
Employee employee = employeeMapper.getEmpById(id);
return employee;
}
//Controller
//根据id查询
@GetMapping("/emp/{id}")
public Employee getEmpById(@PathVariable("id") Integer id){
Employee employee = employeeService.getEmpById(id);
return employee;
}
-1-1-CacheName/value:
* CacheName/value:将方法返回结果放置哪个缓存中 为数组方式
//将方法返回结果放置在名叫emp的数组中
@Cacheable(cacheNames = {"emp"} )
访问http://localhost:8080/emp/1
—| 首次访问控制台输出查询语句
—| 再次访问控制台不再输出查询语句,直接从缓存中取
两次访问内容:
-1-2-key:
1自定义key:
* key:默认是使用方法参数的值,还可以自定义key
* 自定义key : key="#root.methodName+'['+#id+']'" ===>getEmpById[id]作为key
@Cacheable(cacheNames = {"emp"},key="#root.methodName+'['+#id+']'")
访问http://localhost:8080/emp/1
调试可以发现key为自定义格式
2默认key:
默认key为使用方法参数的值
@Cacheable(cacheNames = {"emp"},key="#id")
访问http://localhost:8080/emp/1
调试可以发现key为方法参数值
-1-3-keyGenerator:
* keyGenerator:自定义生成器 在config包下创建自定义配置类
* eg:keyGenerator="myKeyGenerator"
* @Cacheable(cacheNames = {"emp"},keyGenerator="myKeyGenerator")
//自定义生成器 在config包下创建自定义配置类
package cronos.study_g01_cacher.config;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* @ClassName: MyCacheConfig
* @Author: cronos
* @Version: 1.0
**/
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object o, Method method, Object... objects) {
return method.getName()+"["+ Arrays.asList(objects.toString())+"]";
}
};
}
}
@Cacheable(cacheNames = {"emp"},keyGenerator="myKeyGenerator")
访问http://localhost:8080/emp/1
调试可以发现key为自定义生成器格式
-1-4-condition:
* condition:
* eg:condition = "#a0>1" 表示第一个参数大于1才进行缓存
* @Cacheable(cacheNames = {"emp"},condition = "#a0>1")
-1-5-unless:
* unless:
* eg:unless = "#a0==2" 表示第一个参数值为2 结果不缓存
* @Cacheable(cacheNames = {"emp"},unless = "#a0==2")
-1-6-sync:
* sync:异步模式 开启后 unless不能使用
-2-@CachePut
* @CachePut: 既调用方法 又更新缓存数据 同步更新缓存
* 运行步骤:
* 先运行方法,再将目标结果缓存起来
//Service
@CachePut(value = "emp",key = "#result.id")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
//Controller
//更新员工
@GetMapping("emp")
public Employee updateEmp(Employee employee){
employeeService.updateEmp(employee);
return employee;
}
* 测试步骤
* 1、查询2号员工---->查询结果会放到缓存中 再次查询直接调用缓存
* 2、更新2号员工
* 3、查询2号员工---->还是原来的数据
* 原因:
* 查询和更新的key不同:
* 查询2号员工:key:2 value:张三
* 更新2号员工:返回值放入缓存 key:传入employee对象 value:返回employee对象
* 解决:
* key="#employee.id" 使用传入参数员工id 和查询key统一
* key="#result.id"
* @CachePut(value = "emp",key = "#result.id")
* 注:@Cacheable中的key不能使用"#result.id",因为Cacheable是先缓存再运行
* 效果:
* 第一次查询:查询mysql
* 第二次更新:更新mysql
* 第三次查询:调用内存
-3-@CacheEvict
* @CacheEvict: 缓存清除
* allEntries = true,清除所有缓存数据 默认false
* beforeInvocation=true 缓存清除是否在方法前执行
* true 若程序异常 依旧清除缓存
* 默认false 若程序异常 则不会清除缓存
//Service
@CacheEvict(value = "emp",key = "#id")
public void delEmp(Integer id){
//偷懒:输出即代表删除一条数据
System.out.println("delEmp"+id+"员工");
}
//Controller
//删除
@GetMapping("/delEmp")
public String delEmp(Integer id){
employeeService.delEmp(id);
return "del success";
}
删除的同时也将缓存删除
1.若allEntries = true则删除所有缓存
2.若beforeInvocation=true则删除缓存在方法执行前=>失败也执行,beforeInvocation=flase删除缓存在方法执行后=>失败不执行
-4-@Caching
组合
Cacheable
CachePut
CacheEvict
//Service
//指定多个缓存规则
@Caching(
cacheable = {
@Cacheable(value = "emp",key="#lastName")
},
put = {
@CachePut(value = "emp",key = "#result.id"),
@CachePut(value = "emp",key = "#result.gender")
//@CachePut的存在会让如下方法函数一定执行的
}
)
public Employee getEmpByLastName(String lastName){
System.out.println("getEmpByLastName");
return employeeMapper.getEmpByLastName(lastName);
}
//Controller
//根据名字查询
@GetMapping("/emp/lastName/{lastName}")
public Employee getEmpByLastName(@PathVariable("lastName") String lastName){
return employeeService.getEmpByLastName(lastName);
}
访问http://localhost:8080/emp/lastName/张三执行一次语句查询 并放入缓存中;
访问http://localhost:8080/emp/2查询id=2的员工信息时 直接从缓存中取id=2的员工信息(即张三)
注:
再次访问http://localhost:8080/emp/lastName/依旧会执行一次语句查询 因为**@CachePut**的存在会让方法函数一定执行
补充:
CacheConfig抽取缓存的公共配置
Service:
@CacheConfig(cacheNames = "emp")
@Service
public class EmployeeService {}
Service中注解的value=emp就可省略