Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。
如下图所示:
Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;
要在Springboot中使用缓存需要以下几步:
第一步: 导入spring-boot-starter-cache模块
第二步: @EnableCaching开启缓存
第三步: 使用缓存注解
org.springframework.boot
spring-boot-starter-cache
@SpringBootApplication
@EnableCaching
public class Springboot07CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot07CacheApplication.class, args);
}
}
javaBean如下:
package com.lxj.cache.bean;
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() {}
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 + "]";
}
}
controller层如下:
@RestController
public class EmployeeController {
@Autowired
EmployService employService;
@GetMapping("/emp/{id}")
public Employee getEmpById(@PathVariable("id") Integer id) {
Employee empById = employService.getEmpById(id);
return empById;
}
}
service层如下:
@Service
public class EmployService {
@Autowired
EmploeeMapper emploeeMapper;
/**
* 将方法的运行结果进行缓存,以后要相同的数据,直接从缓存中获取,不用调用方法
* CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一的一个名字
*/
//cacheNames和value都可以
@Cacheable(cacheNames = {"emp"})
public Employee getEmpById(Integer id){
System.out.println("查询" + id + "号员工");
Employee employee = emploeeMapper.getEmpById(id);
return employee;
}
}
mapper接口如下:
@Mapper
public interface EmploeeMapper {
@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 updateEmpById(Employee employee);
@Delete("DELETE * FROM employee WHERE id = #{id}")
public void deleteEmpById(Integer id);
@Insert("INSERT INTO employee(lastName,email,gender,d_id) VALUES(#{lastName},#{email},#{gender},#{dId})")
public void InsertEmp(Employee employee);
@Select("SELECT * FROM employee WHERE lastName = #{lastName}")
public Employee getEmpByLastName(String lastName);
}
application.properties如下:
spring.datasource.username=root
spring.datasource.password=xxxxx
spring.datasource.url=jdbc:mysql://localhost:3306/springCache
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#开启驼峰命名法
mybatis.configuration.map-underscore-to-camel-case=true
#打印sql语句日志
logging.level.com.lxj.cache.mappers=debug
#控制台打印配置信息
debug=true
sql语句如下:
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;
运行项目,打开浏览器输入 http://localhost:8080/emp/1 ,可以看到浏览器返回的值
可以看到并没有查询数据库,而是从缓存中获取的数据,这说明缓存起作用了.
其工作流程参照下面:
方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取 (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
去Cache中查找缓存的内容,使用一个key,默认就是方法的参数
key是按照某种策略生成的,默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key
SimpleKeyGenerator生成key的默认策略:
如果没有参数:key=new SimpleKey()
如果有一个参数:key=参数值(这里就是id的值作为key)
如果有多个参数:key = new SimpleKey(params)
没有查到缓存就调用目标方法
将目标方法返回的结果,放进缓存中
@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存, 如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用
核心:
1)使用CacheManager【ConcurrentMapCacheManager】按照名字获得Cache【ConcurrentMapCache】组件
2)key使用keyGenerator生成,默认是SimpleKeyGenerator
* 原理
* 1.自动配置类:CacheAutoConfigration
* 2.缓存配置类
* 0 = "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration"
* 1 = "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration"
* 2 = "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration"
* 3 = "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration"
* 4 = "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration"
* 5 = "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration"
* 6 = "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration"
* 7 = "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration"
* 8 = "org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration"
* 9 = "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration"
* 10 = "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"
* 3.哪个配置类默认生效:SimpleCacheConfiguration
* 4.给容器中注册了一个CacheManager:ConcurrentMapCacheManager
* 5.可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用是将数据保存在ConcureentMap
几个属性:
cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存.
<----------------------------------------------------------分隔符----------------------------------------------------------------->
key:缓存数据使用的key:可以用他来指定。不指定默认是使用方法参数的值 eg:1-方法的返回值
编写SpEL; #id;参数id的值 #a0 #p0 #root.args[0]( 这种写法参见上面的图SpEL表达式)
eg:@Cacheable(cacheNames = {"emp"},key = "#root.methodName + '[' + #id +']' ")
此时可以看到该缓存中key的值为: ey=“getEmp[1]”
<----------------------------------------------------------分隔符----------------------------------------------------------------->
* keyGenerator:key生成器;可以自己指定key的生成器组件id
* key和keyGenerator二选一使用
在这里要写一个MyCacheConfig类
package nju.software.cache.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;
@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() ;
}
};
}
//在service的方法上指定自定义的key
@Cacheable(cacheNames = {"emp"},keyGenerator = “myKeyGenerator")
此时的key= getEmp[1] (Arrays.asList()自己带了【】)
<----------------------------------------------------------分隔符----------------------------------------------------------------->
* cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
<----------------------------------------------------------分隔符----------------------------------------------------------------->
* condition:指定符合条件的情况下才缓存;
* condition = "#a0 > 0" 代表打一个参数的值大于0才缓存
@Cacheable(cacheNames = {"emp"},keyGenerator = “myKeyGenerator",condition = "#id > 0")
or
@Cacheable(cacheNames = {"emp"},keyGenerator = “myKeyGenerator", condition = "#a0 > 0") //SpEL表达式
<----------------------------------------------------------分隔符----------------------------------------------------------------->
* unless:否定缓存:当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
@Cacheable(cacheNames = {"emp"},keyGenerator = “myKeyGenerator", condition = "#a0 > 0" unless = ‘#a0==1’)
如果第一个参数是1就不缓存
<----------------------------------------------------------分隔符----------------------------------------------------------------->
* sync:是否使用异步模式
@CachePut的功能
@Cacheable(cacheNames = {"emp"})
public Employee getEmp(Integer id){
System.out.println("查询" + id + "号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
@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 update(Employee employee){
Employee emp = employeeService.updateEmp(employee);
return emp;
}
测试的步骤:
@CacheEvict的功能
/**
* @CacheEvict:缓存清楚
* allEntries = true 全部删除
*/
@CacheEvict(value = "emp",key = "#id")
public void deleteEmp(Integer id){
System.out.println("deleteEmp" + id);
}
@Caching的功能
@Caching(
cacheable = {
@Cacheable(value = "emp",key = "#lastName")
},
put = {
@CachePut(value = "emp",key = "#result.id"),
@CachePut(value = "emp",key = "#result.email"),
}
)
public Employee getEmployeeByLastName(String lastName){
return employeeMapper.getEmployeeByLastName(lastName);
}
此时通过lastName来访问数据库,会把对应的返回结果的id和email更新到这个缓存,所以下次在通过id和email来访问这个对象是不需要经过数据库的。
@Cacheable(cacheNames = “emp”)的功能
在service上直接声明,其下的方法上的注解都可以不写value值了