八、SpringBoot与缓存

一、JSR107简介

1、5个核心接口

  1. CachingProvider:定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运che行期间访问多个CachingProvider;
  2. CacheManager:定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有;
  3. Cache:一个类似Map的数据结构并临时存储以key为索引的值。一个Cache仅被一个CacheManager所拥有;
  4. Entry:存储在Cache中key-value对;
  5. Expire:每一个存储在Cache中的条目有一个定义的有效期;

2、JSR107架构图

在这里插入图片描述

3、JSR107引入jar包

<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
</dependency>

二、Spring缓存抽象

  1. org.springframework.cacheCache:为缓存组件提供规范定义,包含缓存的各种集合;提供各种XXXCache的实现;
  2. 每次调用需要缓存功能的方法时,Spring会检查指定参数的指定目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回用户。下次调用直接从缓存中获取。
  3. 使用Spring缓存抽象需要关注两点:
    • 确定方法需要被缓存以及他们的缓存策略;
    • 从缓存中读取之前缓存存储的数据;在这里插入图片描述
  4. 几个重要概念和注解
说明
Cache缓存接口、定义缓存操作。如:RedisCache
CacheManager缓存管理器、管理各种缓存组件
@Cacheable主要针对方法配置,能根据方法的请求参数对其结果进行缓存
@CacheEvict清空缓存
@EnableCaching开启基于注解的缓存
keyGenerator缓存数据时key生成策略
serialize缓存数据时value序列化策略

三、代码演示

1、环境搭建

  1. 导入数据库文件,创建出department和employee表
  2. 创建Javabean封装数据
  3. 整合Mybatis操作数据库
    • 配置数据源信息
    • 使用注解版Mybatis:@MapperScan指定需要扫描的mapper接口所在的包

搭建基本环境详见第六章:SpringBoot Data整合Mybatis部分

2、快速体验缓存

  1. 步骤:

    • 开启基于注解的缓存:@EnableCaching
      @MapperScan(basePackages = "cn.hymll.cache.springboot.mapper")
      @SpringBootApplication
      @EnableCaching
      public class Springboot01CacheApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(Springboot01CacheApplication.class, args);
          }
      
      }
      
    • 标注缓存注解即可
          @Cacheable(cacheNames = "emp")
          public Employee getEmp(Integer id){
              System.out.println("查询 " + id + "号员工");
              return employeeMapper.getEmpById(id);
          }
      
  2. @Cacheable注解:

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Cacheable {
    
       @AliasFor("cacheNames")
       String[] value() default {};
    
       @AliasFor("value")
       String[] cacheNames() default {};
    
       String key() default "";
    
       String keyGenerator() default "";
    
       String cacheManager() default "";
    
       String cacheResolver() default "";
    
       String condition() default "";
    
       String unless() default "";
       
       boolean sync() default false;
    
    }
    
    1. 将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存获取,不要调用方法
    2. CacheManager管理多个Cache组的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组有自己唯一的名字
    3. 几个属性:
      • cacheNames/value:指定缓存组件的名字;将方法的返回值放到那个缓存中,是数组的形式,可以指定多个缓存;
      • key:缓存数据使用的key可以用它指定。默认使用方法参数的值。1-方法的返回值;
        编写SpEL: #id;参数id的值 <==> #a0 #p0 #root.args[0]
      • keyGenerator:key生成器;可以指定key生成器的组件id;key/keyGenerator:二选一使用;
        Key示例:key = "#root.methodName + '[' + #id + ']'"
        keyGenerator:keyGenerator = "myKeyGenerator"
        @Configuration
        public class MyCacheConfig {
        
            @Bean("myKeyGenerator")
            public KeyGenerator keyGenerator(){
                return (target, method, params) -> method.getName() + "[" + Arrays.asList(params).toString() + "]";
            }
        }
        
      • cacheManager:指定缓存管理器,或者cacheResolver指定获取解析器
      • condition:指定符合条件的情况下才缓存;例如:condition = "#id > 1":第一个参数值大于1才缓存
      • unless:否定缓存;当unless指定条件为true,方法的返回值就不会缓存,可以获取到结果进行判断,如:unless = "#a0 ==2":第一个参数值是2.就不缓存
      • sync:是否使用异步模式,在异步模式下,unless不支持
  3. spEL表达式

名称位置描述示例
methodNameroot object当前被调用的方法名#root.methodName
methodroot object当前被调用的方法#root.method.name
targetroot object当前被调用的目标对象#root.target
targetClassroot object当前被调用的目标对象类#root.targetClass
argsroot object当前被调用的方法的参数列表#root.args[0]
cachesroot object当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”,“cache2”})),则有两个cache#root.caches[0].name
argument nameevaluation context方法参数的名字,可以直接 #参数名,也可以使用#p0或#a0 的形式,0代表参数的索引;#ban、#a0、#p0
resultevaluation context方法执行后的返回值(仅当方法执行之后的判断有效,如 ‘unless’,'cache put’的表达式 ’cache evict‘的表达式beforeInvocation=false)#result
  1. @CachePut注解:即调用方法,又更新缓存数据;同步更新缓存
    一般用于修改了数据库的某个数据,同时更新缓存;

    1. 运行时机:

      • 先调用目标方法;
      • 将目标方法的结果缓存起来;
    2. 测试步骤:

      • 查询1号员工;查到的结果放到缓存中;key:1 value:张三
      • 以后查询还是之前的结果
      • 更新1号员工:lastName:zhangsan;gender:0
        将方法的返回值也放进缓存了;
        key:传入的employee对象 value:返回的employee对象
      • 查询1号员工?
        应该是更新后的员工:
        key = “#employee.id”:使用传入参数的员工id
        key = “#result.id”:使用返回后的id
        @Cacheable的key是不能用result
        为什么是没更新前的数据?【1号员工没有在缓存中更新】
  2. @CacheEvict注解: 缓存清除

    • key:指定要清除的key
    • allEntries:清除当前缓存所有的数据
    • beforeInvocation:缓存的清除是否在方法之前执行
      默认代表是在方法执行之后执行;如果出现异常缓存就不会清除
      beforeInvocation = true:代表清除缓存操作是在方法之前,无论方法是否出现异常,缓存都清除
  3. @Caching:定义复杂缓存规则

        @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){
            return employeeMapper.getEmpByLastName(lastName);
        }
    
  4. @CacheConfig:抽取缓存的公共配置

    @CacheConfig(cacheNames = "emp")
    @Service
    public class EmployeeService {}
    

3、原理:

  1. 自动配置类:CacheAutoConfiguration
  2. 缓存的配置类:
    在这里插入图片描述
  3. 默认配置生效SimpleCacheConfiguration
  4. 给容器中注册了一个cacheManager:ConcurrentMapCacheManager
  5. 可以获取和创建ConcurrentMapCache类型的缓存组件,他的作用将数据保存在ConcurrentMap中

4、运行流程:@Cacheable

  1. 方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定名字获取;(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件则自动创建
  2. 去Cache中查找缓存的内容,使用一个key,默认就是方法参数;key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimplekeyGenerator生成key
    • 生成key策略:
      • 如果没有参数:key=new SimpleKey();
      • 如果有一个参数:key=参数的值
      • 如果有多个参数:key=new SimpleKey(params)
  3. 没有查到缓存就调用目标方法;
  4. 将目标方法返回结果放进缓存

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

5、核心:

  1. 使用cacheManager【ConcurrentMapCacheManager】按照名字获取Cache【ConcurrentMapCache】组件
  2. key是使用keyGenerator生成的,默认使用SimplekeyGenerator

四、整合redis作为缓存

默认使用的是ConcurrentMapCacheManager=ConcurrentMapCache:将数据保存在ConcurrentMap<Object,Object>中;开发中使用缓存中间件:redis、memcached、和chcache等;

1、redis安装

  1. redis的docker安装
#拉取redis的docker官方镜像
docker pull redis
#查看镜像ID
docker images
#启动一个redis容器
docker start -d -p 6379:6379 --name myredis imageId

注意:redis详细说明请参考相关的redis教程;

2、引入redis的starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

3、SpringBoot配置redis

spring:
  redis:
  	#配置redis的主机IP
    host: 192.168.1.102
    #redis如果有密码的话配置redis密码
    #password: redis
    #配置使用redis的哪一个数据库
    database: 2

4、代码测试redis是否可以使用

  1. 配置自定义的RedisTemplate
    @Bean
    public RedisTemplate<Object, DmComponentIo> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, DmComponentIo> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setDefaultSerializer(new Jackson2JsonRedisSerializer<>(Employee.class));
        return template;
    }
  1. 测试代码
@SpringBootTest
class Springboot01CacheApplicationTests {

    @Autowired
    DmComponentIoMapper mapper;

    @Autowired
    StringRedisTemplate stringRedisTemplate;//操作k-v都是字符串

    @Autowired
    RedisTemplate<Object, Object> redisTemplate;//k-v都是对象的

    @Autowired
    RedisTemplate<Object, DmComponentIo> empRedisTemplate;//k-v都是对象的

    /**
     * Redis常见的五大数据类型
     *  String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
     *  stringRedisTemplate.opsForValue()[String(字符串)]
     */
    @Test
    public void TestRedis01(){
        //给redis中保存了一个数据
        //stringRedisTemplate.opsForValue().append("msg","hello");
        final String msg = stringRedisTemplate.opsForValue().get("msg");
        System.out.println("msg = " + msg);
    }

    /**
     * 测试保存对象
     */
    @Test
    public void TestRedis02(){
        final DmComponentIo dmComponentIo = mapper.getDmComponentIoById(15835L);
        //默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中
        //redisTemplate.opsForValue().set("emp-01",dmComponentIo);
        //1.将数据以JSON的方式保存
        //2、redisTemplate默认的序列化规则:改变默认的序列化规则
        empRedisTemplate.opsForValue().set("emp-02",dmComponentIo);
    }
 }

5、测试缓存

原理:CacheManager===Cache 缓存组件老实际给缓存中存取数据

  1. 引入redis的starter,容器中保存的是RedisCacheManager
  2. RedisCacheManager帮我们创建RedisCache来作为缓存组件,RedisCache通过操作redis缓存数据
  3. 默认保存数据k-v都是Object;利用序列化保存,如何保存JSON
    • 引入了redis的starter,CacheManager变为RedisCacheManager
    • 默认创建的 RedisCacheManager 操作redis时使用的是 RedisTemplate<Object, Object>
    • RedisTemplate<Object, Object>默认使用JDK序列化机制;使用其他序列化方式可以使用自定义的RedisTemplate;
  4. 自定义CacheManager:
    缓存数据能存入redis
    第二次从缓存中查询就不能反序列化回来;
    存的是dept的JSON数据;CacheManager默认使用RedisTemplate<Object,Employee>操作redis
        @Bean("employeeCacheManager")
        @Primary//将某个配置作为默认使用的
        public RedisCacheManager employeeCacheManager(RedisConnectionFactory redisConnectionFactory){
            RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(Duration.ofSeconds(10)) //设置过期时间
                    .disableCachingNullValues()   //禁止缓存null对象
                    //* .computePrefixWith(cacheName -> "yourAppName".concat(":").concat(cacheName).concat(":"))*//* //此处定义了cache key的前缀,避免公司不同项目之间的key名称冲突
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) //定义了key和value的序列化协议,同时hash key和hash value也被定义
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Employee.class)));
    
            return RedisCacheManager.builder(redisConnectionFactory)
                    .cacheDefaults(cacheConfiguration)
                    .build();
        }
    
  5. 使用代码的方式操作缓存
    public Employee getEmp(Integer id){
        System.out.println("查询 " + id + "号员工");
        final Employee empById = employeeMapper.getEmpById(id);
        //通过注入employeeCacheManager来操作缓存
        employeeCacheManager.getCache("emp");
        
        return empById;
    }
**注意:如果使用的自定义的RedisCacheManager & RedisTemplate,只会实例化特定的Bean,这时我们可以通过`cacheManager = "employeeCacheManager"`来单独设置CacheManager;**
1. 在Service类上设置,类中所有需要缓存的方法都起作用;
```java
@CacheConfig(cacheNames = "emp",cacheManager = "employeeCacheManager")
@Service
public class EmployeeService {}
```
2. 单独某个方法上设置,只对这个方法起作用
```java
    @Cacheable(cacheNames = "emp",cacheManager = "employeeCacheManager")
	public Employee getEmp(Integer id){}
```
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天使吻过的BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值