SpringBoot(十一)——SpringBoot与缓存

文章目录

    • 1. JSR107
    • 2. SpringBoot缓存抽象
      • 2.1 几个重要概念&缓存注解
      • 2.2 @Cacheable/@CachePut/@CacheEvict 主要的参数
      • 2.3 缓存可用的SpEL表达式
      • 2.4 使用缓存
      • 2.4 SpringBoot 缓存原理
      • 2.5 运行流程
      • 2.6 其他缓存注解的使用
    • 3. SpringBoot 整合Redis
      • 3.1 整合步骤
      • 3.2 RedisTemplate
      • 3.3 Redis 缓存原理

缓存,对一个成熟的系统来说是必不可少的,对于一些热点数据,一般都会放在缓存里,这样每次访问的时候,就不需要从数据库中获取了。

1. JSR107

JSR107规范中定义了缓存相关的接口,使用JSR107整合系统的难度比较大,所以很少用,一般使用的是SpringBoot的缓存抽象,SpringBoot缓存抽象中的底层概念和JSR107规范中的是通用的。
SpringBoot(十一)——SpringBoot与缓存_第1张图片
SpringBoot(十一)——SpringBoot与缓存_第2张图片

cacheManager就类似于数据库连接池,cahce类似于不同的数据源。

2. SpringBoot缓存抽象

Spring从3.1开始定义了org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口来统一不同的缓存技术, 并支持使用JCache(JSR-107)注解简化我们开发

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合
  • Cache接口下,Spring提供了各种xxxCache的实现;如RedisCacheEhCacheCache , ConcurrentMapCache等;

每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

使用Spring缓存抽象时我们需要关注以下两点

  1. 确定方法需要被缓存以及他们的缓存策略
  2. 从缓存中读取之前缓存存储的数据

2.1 几个重要概念&缓存注解

SpringBoot(十一)——SpringBoot与缓存_第3张图片

  • CacheCacheManager用于管理缓存,对缓存中的数据进行增删改查操作。
  • @Cacheable@CacheEvict@CachePut可以简化开发
    • 在查询方法上标注@Cacheable注解,每次查询,就会将结果放到缓存中
    • 在删除方法上标注@CacheEvict注解,这样,每次删除数据库中数据的同时,也会将缓存中的数据清除,
    • 在更新方法上标注@CachePut,每次更新数据中中某条数据时,也会更新缓存中的某条数据。

2.2 @Cacheable/@CachePut/@CacheEvict 主要的参数

SpringBoot(十一)——SpringBoot与缓存_第4张图片
除了表中的属性之外,还有一些其他属性

  • cacheNames:和value用法一样,指定将数据放入的缓存,都可以使用数组的方式,指定多个缓存
  • keyGenerator:key的生成器,可以自己指定key的生成器的组件(写一个KeyGenerator实现类,并注册到容器中),key和keyGenerator二选一
  • cacheManager:指定缓存管理器,或者使用cacheResolver指定
  • unless:否定缓存,当unless指定的条件为true,不缓存,可以获取到结果进行判断。和condition相反
  • sync:是否使用异步模式

2.3 缓存可用的SpEL表达式

SpringBoot(十一)——SpringBoot与缓存_第5张图片

2.4 使用缓存

基本使用步骤

  1. 引入spring-boot-starter-cache模块
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

  1. @EnableCaching开启缓存(在主配置类上标注)
    SpringBoot(十一)——SpringBoot与缓存_第6张图片

  2. 使用缓存注解,如@Cacheable、@CachePut(用在service中的方法上)
    SpringBoot(十一)——SpringBoot与缓存_第7张图片

2.4 SpringBoot 缓存原理

  1. 首先看缓存的自动配置类CacheAutoConfiguration,这个类给容器中导入了CacheConfigurationImportSelector
    在这里插入图片描述

  2. 接着看CacheConfigurationImportSelector类,此类的selectImports()方法添加了许多缓存的配置类,每个配置类上都有条件,在什么情况下生效,其中SimpleCacheConfiguration默认生效,可以在配置文件中加上debug=true,查看生效的配置类
    SpringBoot(十一)——SpringBoot与缓存_第8张图片
    在这里插入图片描述
    SpringBoot(十一)——SpringBoot与缓存_第9张图片

  3. SimpleCacheConfiguration类,这个类注册了一个ConcurrentMapCacheManager
    SpringBoot(十一)——SpringBoot与缓存_第10张图片

  4. ConcurrentMapCacheManager类实现了CacheManager接口,重写了getCache(String var1)方法,通过getCache方法获得Cache,如果没有,会创建一个ConcurrentMapCache
    SpringBoot(十一)——SpringBoot与缓存_第11张图片

  5. ConcurrentMapCache使用ConcurrentMap以k-v的方式存储缓存,并且ConcurrentMapCache中有很多操作缓存的方法
    SpringBoot(十一)——SpringBoot与缓存_第12张图片

2.5 运行流程

@Cacheable为例(在getCacheConcurrentMapCache类的lookupput,以及@Cacheable标注的方法中打断点分析)

  1. Service中查询方法运行之前,先按照cacheName指定的名字获取Cache(CacheManager中getCaceh方法获取相应的缓存),第一次获取的时候,因为Cache为null,所以会调用createConcurrentMapCache(name)方法创建,之后,把创建好的ConcurrentMapCache放在cacheMap中。
  2. 去Cache中查找缓存的内容,使用一个key,默认就是方法的参数,key是按照某种策略生成的;默认是使用keyGenerator生成的,SimpleKeyGenerator实现了keyGenerator接口,所以默认使用SimpleKeyGenerator生成key

SimpleKeyGenerator生成key的默认策略

  • 如果没有参数;key=new SimpleKey();
  • 如果有一个参数:key=参数的值
  • 如果有多个参数:key=new SimpleKey(params);
    SpringBoot(十一)——SpringBoot与缓存_第13张图片
  1. 如果没有查到缓存,就调用目标方法(Service中@Cacheable标注的方法)
  2. 将目标方法返回的结果放进缓存

总结

@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;

核心

  1. 使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
  2. key使用keyGenerator生成的,默认是SimpleKeyGenerator

2.6 其他缓存注解的使用

//@CacheConfig标注在类上,用于抽取@Cacheable的公共属性
//由于一个类中可能会使用多次@Cacheable等注解,所以各项属性可以抽取到@CacheConfig
@CacheConfig(cacheNames = "emp")
@Service
public class EmpolyeeService {
    @Autowired
    EmployeeMapper employeeMapper;

    @Cacheable(value="emp" , key="#id")
    public Employee selectById(Integer id) {
        System.out.println("查询了"+ id +"员工");
        Employee employee = employeeMapper.selectById(id);

        return employee;
    }

    /*@CachePut及调用方法,有更新缓存数据
    修改了数据库中的某个数据,有更新缓存
     运行时机:
        1.先调用目标方法
        2.将目标方法的结果缓存起来
      测试步骤:
        1.查询1号员工,查询结果将放到缓存中,再次查询的话,就不会访问数据库
        2.更新1号员工
        3.再次查询1号员工,若果不指定key的话,key就是方法的参数,和查询的key,不一样
          得到的就不是修改之后的数据,而是没有修改的数据。
          指定key为id:
              key="#emp.id"
              key="#result.id",注意,@Cacheable不能使用这个,因为@Cacheable
              在目标执行方法执行之前就获得key,这时候,结果为空,会报空指针异常

     */
    @CachePut(value="emp" ,key="#emp.id" )
    public Employee update(Employee emp) {
        System.out.println("更新了" + emp);
        employeeMapper.update(emp);
        return emp;
    }

    /*@CacheEvict 清除缓存
        key:指定要删除的数据
        allEntries=true,删除这个缓存中的所有数据,默认是false
        beforeInvocation,缓存的清除是否在方法之前执行,默认是false,是在方法
            之后执行的,如果方法出错,缓存就没法清除,如果设为true,表示在方法
            之前执行,无论方法是否出错,缓存都会清除
     */
    @CacheEvict(value="emp")
    public void deleteById(Integer id) {
        System.out.println("删除了" + id);
        //这里只删除缓存中的数据,不删除数据库中的
        //employeeMapper.deleteById(id);
    }

    /*@Caching ,可以指定多个缓存规则
          public @interface Caching {
             Cacheable[] cacheable() default {};
             CachePut[] put() default {};
             CacheEvict[] evict() default {};
          }
     */

    @Caching(
            cacheable = {@Cacheable(value="emp",key="#lastName")},
            put = {@CachePut(value="emp", key="#result.id"),
                    @CachePut(value="emp", key="result.gender")}
            )
    public Employee selectByLastame(String lastName) {
        Employee employee = employeeMapper.selectByLastname(lastName);
        return employee;
    }
}

3. SpringBoot 整合Redis

开发中,一般使用的是缓存中间件,比如Redis、memCache、EhCache,根据前面SpringBoot的缓存原理可知,你要使用哪个缓存,就使用哪个缓存的xxxCacheConfigura类,只要导入相应的缓存类之后,就会注册相应的xxxCacheConfiguration

3.1 整合步骤

  1. 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

  1. 在配置文件中指定Redis服务器地址
spring.redis.host=47.94.231.234

3.2 RedisTemplate

RedisAutoConfiguration向容器中导入了两个类

  • redisTemplate:操作的k-v都为对象
  • StringRedisTemplate:操作的k-v都为字符串

Redis 常用的五种数据类型

String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)

RedisTemplate类中的opsForValue方法操作字符串,还有opsForXxx来操作相应的数据类型,相当于获取的是一种操作对象,使用操作对象来操作相应的数据结构

操作字符串
SpringBoot(十一)——SpringBoot与缓存_第14张图片

操作对象

RedisCache进行缓存数据,要缓存的对象的类要实现Serializable接口,否则会报错,默认情况下是以jdk序列化数据存在redis中

在这里插入图片描述
一般是以json的形式将数据存储在redis中。将对象转换成json

  1. 自己将数据转换为json形式
  2. 自定义RedisTemplate,因为RedisTemplate中默认使用的是JDK自带的序列化器
    SpringBoot(十一)——SpringBoot与缓存_第15张图片
    自定义redisTemplate
    SpringBoot(十一)——SpringBoot与缓存_第16张图片
    序列化数据如下
    SpringBoot(十一)——SpringBoot与缓存_第17张图片

注意,这里必须用GenericJackson2JsonRedisSerializer进行value的序列化解析,如果使用Jackson2JsonRedisSerializer,序列化的json没有"@class": "cn.edu.ustc.springboot.bean.Employee",在读取缓存时会报类型转换异常。

3.3 Redis 缓存原理

  1. 引入Redis的starter,RedisCacheConfiguration会生效,RedisCacheConfiguration向容器中注册了RedisCacheManager
    SpringBoot(十一)——SpringBoot与缓存_第18张图片
  2. RedisCacheManager 创建RedisCache来作为缓存组件RedisCacheRedisCache操作Redis缓存数据,也就是说,当你导入Redis的启动器,再使用注解的话,数据会保存到Redis中
  3. RedisCacheConfiguration中设置了JDK的序列化器JdkSerializationRedisSerializer
    SpringBoot(十一)——SpringBoot与缓存_第19张图片
    如果我们想要以json的形式保存数据,也可以自定义CacheManager,使用这种方法,缓存数据的时候,都会转换成jsong格式。
@Configuration
public class RedisConfig {
    //自定义cacheManager来设置序列化器为GenericJackson2JsonRedisSerializer
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(1))
                .disableCachingNullValues()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();
    }
}

如有不足之处,欢迎指正,谢谢!

你可能感兴趣的:(spring,boot,SpringBoot,redis,学习笔记)