SrpingBoot(八)——玩玩Spring缓存

JSR107规范

玩Java要有仪式感,因此先来看看JSR107对缓存的规范。在Java Caching定义了5个核心接口

  • Entry:存储在Cache的键值对
  • Cache:K ——V结构,以key为索引值,一个Cache只能被一个CacheManager所拥有
  • CacheManager:顾名思义就是对Cache的管理,比如创建,配置,获取,管理和控制这些K——V结构的Cache。
  • CachingProvider:用来管理CacheManager
  • Expiry:定义有效期相关

他们的关系就如下图所示
在这里插入图片描述
用户可以一次访问一个或多个CacheProvider,一个Provider下又有一个或者多个CacheManager,CahceManager又对Cache进行管理。其实吧,我认为他这个分层有些冗余。
正因为这种缓存规范的不合理,因此很多情况下都会用Spring的缓存抽象

Spring的缓存抽象

在Spring中主要用了JSR107的Cache,CacheManager来管理缓存,当然如果要使用完整的JSR107规范也可以。

下面是Spring中提供的一些缓存注解
在这里插入图片描述
@Cacheable和@CachePut的区别就是,Cacheable对请求方法进行缓存,而CachePut既保证方法被调用又要缓存,因此前者一般用来加缓存,后者一般来更新缓存。

在使用这些缓存注解前,要先开启基于注解的缓存,并且设定key的生成策略和对value的序列化(到底是使用简单字符串还是JSON等等)。

缓存初体验

  1. 建立实体类

Employee


//实现序列化接口是为了后边reids可以缓存对象
public class Employee implements Serializable {
	
	private Integer id;
	private String lastName;
	private String email;
	private Integer gender; //性别 1男  0女
	private Integer dId;
   //getter
   //setter
   }

Department

public class Department {
	
	private Integer id;
	private String departmentName;
	//getter
	//setter
  1. 配置Mysql数据库连接
spring:
  datasource:
    username: root
    url: jdbc:mysql:///cache?serverTimezone=GMT&useSSL=FALSE
    password: wrial.qq.com
    type: com.zaxxer.hikari.HikariDataSource

#开启驼峰命名
mybatis:
  configuration:
    map-underscore-to-camel-case: true
#自动配置报告
#debug=true
#Redis,配置的是虚拟机上的redis
spring.redis.host=1270.0.01
  1. 编写Mapper
package com.wrial.cahcedemo.mapper;

import com.wrial.cahcedemo.dao.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 deleteEmp(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}")
    Employee getEmpByLastName(String lastName);
}
  1. 开启缓存注解
@MapperScan("com.wrial.cahcedemo.mapper")
@SpringBootApplication
@EnableCaching//开启缓存注解
public class CacheDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(CacheDemoApplication.class, args);
    }
}
  1. 编写Service

@Cacheable:可缓存

 * 缓存说明* 默认的采用SimpleCache,底层使用ConconcurrentMap。
 * cacheNames/value:二选一,指定缓存名,可以是一个数组类型
 * CacheManager:很多个缓存名所标识的缓存都让CacheManager来管理,也可以指定缓存管理器(如Redis,Mem,或者ConcurrentHashMap等)
 * CacheResolver:和缓存管理器二选一
 * key:缓存使用的key,默认是传入的参数,也可以自定义keyGenerator,也可以使用Spel表达式
 * keyGenerator():key的生成器,默认以传入的参数为key,也可以自定义,和key二选一(有了key就不需要KeyGenerator来自动生成key了)
 * Condition:可以指定条件,也可以使用Spel(如#{id>0})
 * Unless:否定缓存,当Unless为true,那就不会缓存,
 * 也可以获取到结果再判断(如#{result==null}),作用和Condition相反,但是可以一起使用进行双重判断
 * sysc:是否异步,用了以不就不能用unless了

@CachePut:既调用方法,又更新数据

 * 修改了数据库某个值同步更新
 *能导致缓存同步失败的几个原因?
 * 1.value不相同
 * 2.key不相同,在查询的时候是以id为key,如果update以employee对象key就会导致同步失败
 * 也可以使用#result.id
 * 和Cacheable的区别:Cacheable主要是在缓存前起作用进行一些列判断要不要缓存
 * 而cacheput是缓存后起作用,因此Cacheable不能使用result。
* 测试步骤:
 * 1.先查询
 * 2.update
 * 3.查询

@CacheEvict 删除缓存

 * key 指定要删除的元素
 * allEntries = false 默认为false,如果为true删除当前value/name下的所有缓存
 * beforeInvocation = false 默认在方法执行之后删除数据,
 * 如果为true,那就在执行方法之前(可以在方法中写一个异常,查看是否清除异常  

@Caching Cacheable,CachePut和CahceEvict的集合

 * public @interface Caching {
 * 	Cacheable[] cacheable() default {};
 * 	CachePut[] put() default {};
 * 	CacheEvict[] evict() default {};


@Service
//@CacheConfig//顾名思义,写在类上就是可以批量的声明某一类缓存,
// 或者是按照某一种生成更改规则,不用在每一个方法上写,减少了代码的重复性
@Slf4j
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

    
    //使用默认的缓存Key生成策略,也就是传入的参数
    @Cacheable(value = "#{emp}", key = "#id")//标识可缓存(运行结果)
    public Employee getEmp(Integer id) {
        return employeeMapper.getEmpById(id);
    }

    //使用Spel自定义Key,比如自定义生成方法名和id的组合
    @Cacheable(cacheNames = "#{emp}", key = "#root.methodName+'['+'#{id}'+']'")
    public Employee getEmp02(Integer id) {
        return employeeMapper.getEmpById(id);
    }

    //使用KeyGenerator自定义Key,比如自定义生成方法名和id的组合myKeyGenerator是自定义到bean里的
    @Cacheable(cacheNames = "#{emp}", keyGenerator = "myKeyGenerator")
    public Employee getEmp03(Integer id) {
        return employeeMapper.getEmpById(id);
    }

    //Condition,如果第一个参数大于1才给缓存
    @Cacheable(cacheNames = "#{emp}", condition = "#a0>1")
    public Employee getEmp04(Integer id) {
        return employeeMapper.getEmpById(id);
    }

    //如果第一个参数大于1才给缓存
    @Cacheable(cacheNames = "#{emp}", condition = "#a0>1", unless = "#a0==2")
    public Employee getEmp05(Integer id) {
        return employeeMapper.getEmpById(id);
    }


    @CachePut(value = "#{emp}", key = "#employee.id")
    public Employee updateEmp(Employee employee) {
        log.info("员工更新信息-》{}", employee);
        employeeMapper.updateEmp(employee);
        return employee;
    }

  

    @CacheEvict(value = "#{emp}", key = "#id")
    public void deleteEmp(Integer id) {
        System.out.println("deleteEmp" + id);
//        employeeMapper.deleteEmp(id);
    }

    @CacheEvict(value = "#{emp}", allEntries = true)
    public void deleteAllEmp() {
        System.out.println("deleteAllEmp");
    }

    @CacheEvict(value = "#{emp}", beforeInvocation = true, key = "#id")
    public void deleteEmpBefore(Integer id) {
        int i = 1 / 0;
        System.out.println("deleteEmpBefore");
    }

     * 可以满足复杂的缓存条件
     * */

    @Caching(cacheable = {
            @Cacheable(value = "emp",key = "#lastName")
    },
    put = {
            @CachePut(value = "emp",key = "#result.lastName"),
            @CachePut(value = "#{emp}",key = "#result.id")//设置为#{emp}是为了测试上边的查询方法
    })
    public Employee getEmpByLastName(String lastName) {
        return employeeMapper.getEmpByLastName(lastName);
    }

}

Spel表达式获取指定数据的方法
在这里插入图片描述

  1. 编写controller

@Slf4j
@RestController
public class EmployeeController {

    @Autowired
    EmployeeService employeeService;

    @GetMapping("/emp/{id}")
    public Employee getEmployee(@PathVariable("id") Integer id) {
        Employee emp = employeeService.getEmp(id);
        return emp;
    }

    //测试自定义缓存策略
    @GetMapping("/emp01/{id}")
    public Employee getEmployee01(@PathVariable("id") Integer id) {
        Employee emp = employeeService.getEmp02(id);
        return emp;
    }

    //测试自定义KeyGenerator
    @GetMapping("/emp02/{id}")
    public Employee getEmployee02(@PathVariable("id") Integer id) {
        Employee emp = employeeService.getEmp03(id);
        return emp;
    }

    //测试Condition,如果id大于1才给缓存
    @GetMapping("/emp03/{id}")
    public Employee getEmployee03(@PathVariable("id") Integer id) {
        Employee emp = employeeService.getEmp04(id);
        return emp;
    }

    //测试Condition,如果id大于1才给缓存,unless如果满足就不缓存,因此一号二号都不缓存
    @GetMapping("/emp04/{id}")
    public Employee getEmployee04(@PathVariable("id") Integer id) {
        Employee emp = employeeService.getEmp05(id);
        return emp;
    }

    @GetMapping("/emp/update")
    public Employee updateEmp(Employee employee) {
        Employee employee1 = employeeService.updateEmp(employee);
        return employee1;
    }

    //根据特定id删除指定缓存
    @GetMapping("/emp/delete/{id}")
    public void deleteEmp(@PathVariable("id") Integer id) {
        employeeService.deleteEmp(id);
        log.info("success!");
    }

    //删除所有缓存数据
    @GetMapping("/emp/deleteAll")
    public void deleteAllEmp() {
        employeeService.deleteAllEmp();
        log.info("deleteAllEmp Success!");
    }

    //在方法执行之前删除缓存
    @GetMapping("/emp/deleteBefore/{id}")
    public void deleteEmpBefore(@PathVariable("id") Integer id) {
        employeeService.deleteEmpBefore(id);
        log.info("deleteBefore!");
    }

    @GetMapping("/emp/lastName/{lastName}")
    public void getEmpByLastName(@PathVariable("lastName") String lastName) {
        Employee empByLastName = employeeService.getEmpByLastName(lastName);
        log.info("empByLastName->{}", empByLastName);
    }

}

下图就是Spring缓存策略结构图
在这里插入图片描述

测试:http://localhost:8080/emp/2
第一次访问
在这里插入图片描述
在这里插入图片描述
第二次访问就不会从数据库了

当然可以添加自己的设置。

SpringBoot Cache自动配置原理

SpringBoot是怎样对在properties中自动配置不同缓存策略进行自动配置的?
思路:对于SpringBoot的自动配置,一般的命名方式都是xxxAutoConfiguration

  1. 查看CahceAutoConfiguration,并且找到对这些缓存组件加载的方法(Selector)
static class CacheConfigurationImportSelector implements ImportSelector {

		@Override
		public String[] selectImports(AnnotationMetadata importingClassMetadata) {
			CacheType[] types = CacheType.values();
			String[] imports = new String[types.length];
			for (int i = 0; i < types.length; i++) {
				imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
			}
			return imports;
		}
  1. 进行断点调试,并查看类加载的信息
    在这里插入图片描述
    可以看到调试信息,下面都是一些缓存组件,比如JCACHE(JSR107),EHCACHE,REDIS等等
    在这里插入图片描述
    这些都是在mapping添加的
    在这里插入图片描述
    根据容器中存在Cahce和CacheCondition,在没有CacheManager的使用配置
@Configuration
@ConditionalOnBean(Cache.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class GenericCacheConfiguration {

	private final CacheManagerCustomizers customizers;

	GenericCacheConfiguration(CacheManagerCustomizers customizers) {
		this.customizers = customizers;
	}

	@Bean
	public SimpleCacheManager cacheManager(Collection<Cache> caches) {
		SimpleCacheManager cacheManager = new SimpleCacheManager();
		cacheManager.setCaches(caches);
		return this.customizers.customize(cacheManager);
	}

@Cacheabble的运行流程
3. 第一次CacheManager先去Cache中去查找(缓存组件),按照cacheNames指定的缓存名获取,如果没有此缓存就创建一个Cache组件
4. 使用key去Cache中查找缓存内容(默认是方法使用SimpleKeyGenerator定义生成key策略)
5. 没有查询到缓存就调用目标方法

下一次在实战的时候给它会加上缓存的格式是JSON格式还是其他格式的配置

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值