1、JSP107
java Caching定义了5个核心接口,分别是 CachingProvider, CacheManager,Cache,Entry,Expiry
- CachingProvider:定义了创建、配置、获取、管理和控制多个ChacheManager。一个应用可以在运行期间访问多个CachingProvider。
- CacheManager:定义了创建、配置、获取和管理、控制多个唯一命名的Cahce。这些Cache存在于CacheManager的上下文中,一个CacheManager仅被一个CachingProvider所拥有。
- Cache:是一个类似Map的数据结构并临时村吃以key为索引的值。一个Cache仅被一个CacheManager所拥有。
- Entry:是一个存储在Cache中的key-value对。
- Expiry:每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
-
2、Spring 缓存抽象
Spring3.1 开始定义了
org.springframework.cache.Cache
org.springframework.cacche.CacheManager 接口来统一不同的缓存技术;
并支持使用JCache(JSR-107)注解简化我们开发;
Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
- Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
- 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
- 使用Spring缓存抽象时我们需要关注以下两点;
1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据
3、几个重要概念&缓存注解
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCaChe、ConcurrentMapCache等 |
---|---|
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@EnableCaching | 开启基于注解的缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存。 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
/**
* 搭建基本环境
* 1、导入数据库文件 创建出 department 和 employee表
* 2、创建 javaBean 封装数据
* 3、整合 Mybatis 操作数据库
* 1.配置数据源信息
* 2.使用注解版的 MyBatis
* 1)、@MapperScan 指定需要扫描的 mapper 接口所在的包
*/
@MapperScan("com.atguigu.springboot.mapper")
@SpringBootApplication
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
4.环境配置
配置文件 application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/spring_cache
spring.datasource.username=root
spring.datasource.password=000823
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#开启驼峰命名匹配规则
mybatis.configuration.map-underscore-to-camel-case=true
两个javaBean department employee
package com.atguigu.springboot.bean;
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 + "]";
}
}
package com.atguigu.springboot.bean;
public class Employee {
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 + "]";
}
}
EmployeeMapper接口
@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 insertEmp(Employee employee);
}
测试EmployeeMapper
@SpringBootTest
class Springboot01CacheApplicationTests {
@Autowired
EmployeeMapper employeeMapper;
@Test
void contextLoads() {
Employee empById = employeeMapper.getEmpById(1);
System.out.println(empById);
}
}
EmployeeService.java
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工:");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
}
EmoloyeeController.java
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee getEmployee(@PathVariable("id") Integer id){
Employee emp = employeeService.getEmp(id);
return emp;
}
}
5.@cacheable注解
* 二、快速体验缓存
* 步骤:
* 1.开启基于注解的缓存
* 2.标注缓存注解即可
* @Cacheables
* @CacheEvict
* @CachePut
*/
@MapperScan("com.atguigu.springboot.mapper")
@SpringBootApplication
@EnableCaching
/**
* 将方法的运行结果进行缓存:以后再要相同的数据直接从缓存中获取不用调用方法
*
* CacheManager管理多个Cache组件的,对缓存真正的crud操作在缓存组件中,每一个缓存组件有自己唯一一个名字;
* 几个属性:
* cacheName/value:指定缓存的名字;
* key:缓存数据时用的 key ;可以用它指定。默认是使用方法参数的值 1-方法的返回值
* 编写 Spel表达式:#id;参数id的值 == #a0,#p0,#root.args[0]
* keyGenerator:key的生成器,可以自己指定key的生成器组件id
* key 和 keyGenerator 二选一
* CacheManager:指定缓存管理器;或者cacheResolver 缓存解析器 二选一
* condition:指定符合条件的情况下才缓存;
* unless:否定缓存;当unless指定的条件为true,方法的返回值不会被缓存;可以获取到结果进行判断
* sync:是否使用异步模式
* @param id
* @return
*/
@Cacheable(cacheNames = "emp")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工:");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
当第二次查找id=1的用户时,不会从数据库进行查找,而是从缓存中进行查找
5.1.缓存工作原理 和 @Cacheable 运行流程
* 原理:
* 1.自动配置类;
* 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.SimpleCacheConfiguration"
* 9 = "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"
* 3.哪个配置类默认生效:SimpleCacheConfiguration
*
* 4.给容器中注册了一个CacheManager: CacheManager ==> ConcurrentMapCacheManager
* 5.可以获取和创建 ConcurrentMapCacheManager 类型的 缓存组件;他的作用是将数据保存在 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 去查询缓存
* 如果没有就运行,并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据
*
* 核心:
* 1.使用 CacheManager【ConcurrentMapCacheMapper】按照名字得到 Cache【ConcurrentMapCache】组件
* 2.key 使用 KeyGenerator生成的,默认是 SimpleKeyGenerator
5.2.@Cacheable的其他属性
5.2.1 sepl 表达式
5.2.3 属性详解
cacheName/value:指定缓存的名字;将方法的缓存结果放在哪个缓存中,数组的方式可以指定多个缓存
key:缓存数据时用的 key ;可以用它指定。默认是使用方法参数的值 1-方法的返回值
编写 Spel表达式:#id;参数id的值 == #a0,#p0,#root.args[0]
* getEmp[2] =====> key = “#root.methodName+’[‘+#id+’]’”
keyGenerator:key的生成器,可以自己指定key的生成器组件id
key 和 keyGenerator 二选一
@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) +"]";
}
};
}
}
CacheManager:指定缓存管理器;或者cacheResolver 缓存解析器 二选一
condition:指定符合条件的情况下才缓存;
condition = “#id > 0”:具体参数的值条件
condition = “#a0 > 0”:第一个参数的值大于 1 进行缓存
unless:否定缓存;当unless指定的条件为true,方法的返回值不会被缓存;可以获取到结果进行判断
unless = “#a0 == 2”:如果第一个参数的值等于 2 ,结果不缓存
* sync:是否使用异步模式
6.其他属性
6.1 @CachePut注解
/**
* @CachePut: 既调用缓存,又更新缓存数据;同步更新缓存
* 修改了数据库的某个是数据,同时更新缓存;
* 运行时机:
* 1.先调用目标方法,
* 2.将目标方法的结果缓存起来
*
* 测试步骤:
* 1.查询 1 号员工,查到的结果会放在缓存中
* 2.以后查询还是之前的结果
* 3.更新 1 号员工
* 将方法的返回值,放入缓存
* key:传入的employee对象 value:返回的employee对象
* 4.查询 1 号员工?
* 应该是更新后的员工【1号员工没有在缓存中更新】,
* key = "#employee" 使用传入的参数员工id
* key = "#result.id"
* @Cacheable的 key 不能用result取结果
*
* 为什么是更新之前的员工信息?
*
*/
@CachePut(value = "emp",key = "#employee.id")
public Employee updateEmp(Employee employee){
System.out.println("update emp"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
6.2 @CacheEvict
/**
* @CacheEvict:缓存清除
* key:指定要删除的数据
* allEntries = true 删除所有缓存数据
* beforeInvocation = false :缓存的清除是否在方法之前执行
* 默认是在方法执行之后执行,如果方法出现异常,不会清除缓存
* beforeInvocation = true:
* 代表清除缓存实在方法执行之前进行的,无论方法是否出现异常缓存都会清除
*/
@CacheEvict(value = "emp" ,key = "#id",allEntries = true)
public void deleteEmp(Integer id){
System.out.println("删除员工");
//employeeMapper.deleteEmpById(id);
}
6.3 @Caching&@CacheConfig
/**
* 定义复杂的缓存规则
* @param lastName
* @return
*/
@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){
Employee empByLastName = employeeMapper.getEmpByLastName(lastName);
return empByLastName;
}
@CacheConfig(cacheNames = {"emp"})//抽取缓存的公共配置
7.整合redis
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
1.安装 redis ,使用 docker;
2.引入 redis 的 starter
3.配置 redis
7.1 docker命令
[root@192 ~]# sudo systemctl start docker
[root@192 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
redis latest ddcca4b8a6f0 11 days ago 105MB
hello-world latest d1165f221234 5 months ago 13.3kB
[root@192 ~]# docker run -d -p 6379:6379 --name myredis redis
[root@192 ~]# docker restart b8f9871d4cbb
7.2 整合测试
引入starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置文件 application.roperties
spring.redis.host=192.168.75.137
@Autowired
StringRedisTemplate stringRedisTemplate;//操作 kv 字符串
@Autowired
RedisTemplate redisTemplate;// kv 都是对象的
@Autowired
RedisTemplate<Object,Employee> empRedisTemplate;// kv 都是对象的
/**
* Redis 常见的五大数据类型
* String(字符串)、List(列表)、set(集合)、Hash(散列)、zSet(有序集合)
* stringRedisTemplate.opsForValue();简化操作string
* stringRedisTemplate.opsForList();[List(列表)]
* stringRedisTemplate.opsForSet();[set(集合)]
* stringRedisTemplate.opsForHash();[Hash(散列)]
* stringRedisTemplate.opsForZSet();[zSet(有序集合)]
* */
@Test
public void test01(){
//给 redis 保存一个数据
//stringRedisTemplate.opsForValue().append("msg","hello");
//读取 key 的值
String msg = stringRedisTemplate.opsForValue().get("msg");
System.out.println(msg);
stringRedisTemplate.opsForList().leftPushAll("mylist","1","2","3");
}
/**
* 测试保存对象
*/
@Test
public void t2(){
Employee empById = employeeMapper.getEmpById(1);
//默认保存对象,使用 jdk 序列化机制,序列化后的数据保存到数据库中
//redisTemplate.opsForValue().set("emp-01",empById);
解决redis显示数据问题
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(
RedisConnectionFactory redisConnectionFactory
){
RedisTemplate<Object,Employee> template = new RedisTemplate<Object,Employee>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(serializer);
return template;
}
}
@Test
public void t2(){
Employee empById = employeeMapper.getEmpById(1);
//默认保存对象,使用 jdk 序列化机制,序列化后的数据保存到数据库中
//redisTemplate.opsForValue().set("emp-01",empById);
//1.将数据以json的方式保存
//自己将对象转为 json
//默认的序列化规则;改变默认的序列化规则
empRedisTemplate.opsForValue().set("emp-01",empById);
}
7.3 测试缓存
解决存取对象序列化问题
/**
* 基于SpringBoot2 对 RedisCacheManager 的自定义配置
* @param redisConnectionFactory
* @return
*/
@Primary//将某个缓存管理器作为默认使用的
@Bean
public RedisCacheManager employeeCacheManager(RedisConnectionFactory redisConnectionFactory) {
//初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//设置CacheManager的值序列化方式为json序列化
RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer);
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
//设置默认超过时期是1天
defaultCacheConfig.entryTtl(Duration.ofDays(1));
//初始化RedisCacheManager
return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
}
}
编码方式进行缓存操作
@Autowired
DepartmentMapper departmentMapper;
@Qualifier("employeeCacheManager")
@Autowired
RedisCacheManager deptCacheManager;
/**
* 使用缓存管理器得到缓存,进行api调用
* @param id
* @return
*/
public Department getDeptById(Integer id){
System.out.println("查询部门");
Department department = departmentMapper.getDeptById(id);
//获取某个缓存
Cache dept = deptCacheManager.getCache("dept");
dept.put("dept:1",department);
return department;
}