一、JSR107规范
Java Caching定义的5个接口:CachingProvider,CacheManager,Cache,ENtry,Expiry
- CachingProvider:定义了创建、配置、获取、管理和控制多个CacheManager,一个应用可以在运行期间访问多个CachingProvider
- CacheManager:定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中,一个CacheManager仅被一个CachingProvider所拥有
- Cache:一个类似Map的数据结构并临时存储以Key为索引的值,一个Cache仅被一个CacheManager所拥有(从缓存管理器中获取缓存,可以进行增删改查)
- Entry:一个存储Cache中的Key-value对
- Expiry:每一个存储在Cache中的条目有一个定义的有效期,一旦超过这个时间,条目为过期状态,一旦过期,条目将不可访问、更新和删除,缓存有效期可以通过ExpiryPolicy设置
二、Spring缓存抽象
Spring定义了org.springframewrok.cache.Cache、org.springframewrok.cache.CacheManager接口来统一不同的缓存技术,并支持使用JSR107注解。
- Cache:缓存接口,定义缓存操作,Cache接口下Spring提供了各种xxxCache的实现:RedisCache、EhCacheCache、ConcurrentMapCache等
- CacheManager:缓存管理器,管理各种缓存(Cache)组件
- @Cacheable:主要针对方法进行配置,能够根据方法的请求参数对其结果进行缓存(例如:public User getUser(Integer id) 注解之后,可以将返回的User对象存入缓存,如果已经存在则不会调用此方法)
- @CacheEvict:清空缓存
- @CachePut:更新缓存,保证方法被调用,又希望结果被缓存(例如:注解 public User updateUser(User user) ,此方法一定会被调用,且会更新到缓存)
- @EnableCaching:开启基于注解的缓存
- keyGenerator:缓存数据时key生成策略
- seriablize:缓存数据时value序列化策略
三、缓存环境搭建
1 搭建基本环境:
1、导入数据库文件,创建department和employee表
2、创建javabean封装数据
3、整合MyBatis操作数据库
(1)application.properties中配置数据源信息
spring.datasource.url=jdbc:mysql://192.168.0.106:3306/spring_cache
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
(2)使用注解版的mybatis
@MapperScan指定需要扫描的mapper接口所在的包
import springboot.bean.Employee;
import org.apache.ibatis.annotations.*;
@Mapper
public interface EmployeeMapper {
@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 deleteEmpById(Integer id);
@Insert("INSERT INTO employee(lastName,email,gender,d_id) VALUES(#{lastName},#{email},#{gender},#{dId})")
public void insertEmployee(Employee employee);
@Select("SELECT * FROM employee WHERE lastName = #{lastName}")
Employee getEmpByLastName(String lastName);
}
import springboot.bean.Department;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface DepartmentMapper {
@Select("SELECT * FROM department WHERE id = #{id}")
Department getDeptById(Integer id);
}
(3)配置@Service
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import springboot.bean.Employee;
import springboot.mapper.EmployeeMapper;
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
public Employee getEmp(Integer id) {
System.out.println("查询"+id+"号员工");
Employee emp=employeeMapper.getEmpById(id);
return emp;
}
}
(4)配置控制类,与web进行交互
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;
import springboot.bean.Employee;
import springboot.mapper.EmployeeMapper;
import springboot.service.EmployeeService;
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeservice;
@GetMapping("/emp/{id}")
public Employee getEmployee(@PathVariable("id") Integer id) {
Employee emp=employeeservice.getEmp(id);
return emp;
}
}
2 快速体验缓存:
步骤:
- 在main中开启基于注解的缓存@EnableCaching
@MapperScan("springboot.mapper")
@SpringBootApplication
@EnableCaching
public class SpringBoot0CacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBoot0CacheApplication.class, args);
}
}
- 标注缓存注解@Cacheable(对方法进行配置)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import springboot.bean.Employee;
import springboot.mapper.EmployeeMapper;
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
/**
* 将方法的运行结果进行缓存,以后再需要相同的数据,可以从缓存中获取,不用调用方法从数据库获取
*
* CacheManager管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,
* 每一个缓存组件有自己唯一的名字
*
* Cacheable中的几个属性:
* cacheNames/value:指定缓存组件的名字(例如有专门给员工的缓存、部门的缓存);将方法的返回结果放在哪个缓存中,以数组的方式,可以指定多个缓存
* key:缓存数据时使用的key:可以进行指定key的方式(key="#root.methodName+'['+#id+']'"),默认是此使用方法的参数(key=id=1)
* keyGenerator:key的生成器,可以自己指定key的生成器的组件id
* keyGenerator/key:二选一使用
* CacheManager:指定缓存管理器;或者cacheResolver指定缓存解析器(二选一)
* condition:指定符合条件的情况下(为true时)才缓存,condition = "#id>0"表示当id>0时才进行缓存
* unless:否定缓存:当unLess指定的条件为true,方法的返回值就不会被缓存
* sync:是否使用异步模式
*
* @param id
* @return
*/
@Cacheable(cacheNames="emp",condition = "#id>0",unless = "#result==null") //如果返回的结果为null,就不缓存了
public Employee getEmp(Integer id) {
System.out.println("查询"+id+"号员工");
Employee emp=employeeMapper.getEmpById(id);
return emp;
}
}
第一次在浏览器中输入:http://localhost:8080/emp/1
时,会调用getEmp方法,从数据库中查询信息,再次查询1号时,会从缓存中根据key(id=1)进行查询1号,而不会再调用getEmp方法。
自定义KeyGenerator方法:
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //配置类
public class MyCacheConfig {
@Bean("myKeyGenerator") //加入到容器中
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
// TODO Auto-generated method stub
return method.getName()+"["+Arrays.asList(params).toString()+"]";
}
};
}
}
调用时在@Cacheable(cacheNames="emp",condition = "#id>0",unless = "#result==null")
中将参数key变成keyGenerator=“myKeyGenerator”
3 缓存中的其他注解说明:
(1)@CachePut
既调用方法,又更新缓存数据(一定会调用该被注解的方法updataEmp)
运行时机:先调用注解的方法updataEmp,再修改数据库的某个数据,同时更新缓存,将目标方法的结果缓存起来。(key="#employee.id"为用对象的id作为key进行缓存)
@Cacheable(cacheNames="emp",condition = "#id>0",unless = "#result==null") //如果返回的结果为null,就不缓存了
public Employee getEmp(Integer id) {
System.out.println("查询"+id+"号员工");
Employee emp=employeeMapper.getEmpById(id);
return emp;
}
@CachePut(cacheNames="emp",key="#employee.id")
public Employee updataEmp(Employee employee) {
System.out.println("更新:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
在控制类中添加:
@GetMapping("/emp")
public Employee update(Employee employee) {
Employee emp=employeeservice.updataEmp(employee);
return emp;
}
步骤:
1、浏览器中查询1号信息http://localhost:8080/emp/1
,此时已存入缓存中了,浏览器返回:
2、更新员工信息:http://localhost:8080/emp?id=1&lastName=zhaowu&gender=0
此时先执行注解的方法得到结果,再根据key值id=1找到数据库中的value,更新数据库同时更新缓存,再次查询时直接从缓存中查找不用再调用getEmp方法了。
注:如果参数中不规定key,则会用默认的以方法中的参数employee作为key,从而不会更新key=id=1的值(因为取数据的方法中key=id),因此需要规定要修改的key为id.
(2)@CacheEvict
缓存清除,清除在缓存中的该数据
- 如果在
@CacheEvict(cacheNames="emp",key="#id")
中添加allEntries=true,则删除缓存emp中所有数据 - 在
@CacheEvict(cacheNames="emp",key="#id")
中添加beforeInvocation=false
意思是缓存的清除是否在方法之前执行(默认为false,缓存清楚在执行方法后,如果在调用方法时出错,就不会清除缓存了)(true时,清除缓存在方法运行之前,无论方法运行是否出错都会清除缓存)
@CachePut(cacheNames="emp",key="#employee.id")
public Employee updataEmp(Employee employee) {
System.out.println("更新:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
@CacheEvict(cacheNames="emp",key="#id")
public void deleteEmp(Integer id) {
System.out.println("删除:"+id+"号员工");
employeeMapper.deleteEmpById(id); //在数据库中删除该信息
}
在控制类中添加:
@GetMapping("/delemp")
public String deleteEmp(Integer id) {
employeeservice.deleteEmp(id);
return "sucess";
}
步骤:
1、查询1号,再次查询时会从缓存中查找
2、删除1号:http://localhost:8080/delemp?id=1
,此时数据库和缓存中已经都删除了该数据
3、再次查询1号时已经查找不到了
ps:该注解只会删除缓存中的数据,数据库中的数据删除是因为调用的方法deleteEmp中有employeeMapper.deleteEmpById(id)
(3)@Caching
包含了三种注解,可以指定复杂的缓存规则
@Caching(
cacheable = {
@Cacheable(value="emp",key="#lastName")
},
put = { //根据员工的id和邮箱都能查找到员工
@CachePut(value="emp",key="#result.id"), //用返回结果的id作为key
@CachePut(value="emp",key="#result.email"),
}
)
public Employee getEmpByLastName(String lastName) {
return employeeMapper.getEmpByLastName(lastName);
}
@GetMapping("/emp/lastname/{lastname}")
public Employee getEmployeeByLastName(@PathVariable("lastname") String lastName) {
return employeeservice.getEmpByLastName(lastName);
}
其中的@Cacheable实现了:可以按照员工的名字作为key进行查询,@CachePut实现了:同时以key=id和key=email的方式存入缓存中,下次再利用员工的id或者email进行查找该员工时,会直接从缓存中查找了,不用再查找数据库.
(4)@CacheConfig
在类前面添加此注解,可以进行配置,可以指定缓存的名字:emp,缓存的keyGenerator,CacheManager等公共的属性,在类里面的方法中就不需要写value="emp"