SpringBoot-缓存-redis

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设置。
    图片说明
          <dependency>
          <groupId>javax.cache</groupId>
          <artifactId>cache-api</artifactId>
          </dependency>

Spring缓存抽象

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

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

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

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

搭建环境

  • 步骤
    开启基于注解的缓存@EnableCaching

    @EnableCaching
    public class SpringbootdemoApplication {...}

    标注缓存注解即可

  • 基本架构
    图片说明

  • sqEL
    图片说明

CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一的一个名字

Cacheable 几个属性

  • cacheNames/Value:指定缓存组的名字

  • key:缓存数据使用可以;可以用它来指定。默认是使用方法参数的值, 1-方法的返回值 K-V 可以编写SQEL
    key="#root.methodName+'[..'+#id +'..]'"

  • keyGenerator:key的生成器;可以自己指定可以的生成器的组件id 可以通过Config类来生成bean对象
    key/Generator二选一使用 【可以进行字符串处理】

      @Bean
      public KeyGenerator keyGenerator() {
          return new KeyGenerator() {
              @Override
              public Object generate(Object target, Method method, Object... params) {
                  return method.getName() + "["+Arrays.asList(params)+"]";
              }
          };
      }
  • cacheManager:指定缓存的管理器;或者cacheResovler指定获取解析器 二选一

  • condition:指定符合条件的情况下才缓存:
    condition="#id>0"

  • unless:否定缓存;当unless指定的条件为true,方法的返回值就不会背缓存,可以获取到结果进行判断
    unless="#id==2" 排除掉2

  • sync:是否使用异步模式

      @Cacheable(cacheNames = "depa",key = "#id")
      public Department getDepartment(Integer id) {
          System.out.println("getDepartment.....");
          return departmentMapper.getDepartment(id);
      }
    + Condition
    condition = "#id>0"

    图片说明
    工作原理

    从自动配置类入手CacheAutoConfiguration
    ||
    \/
    @Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
    在import中一共有十个缓存器
    SimpleCacheConfiguration
    ||
    \/  放了一个ConcurrentMapCacheManager   缓存管理器
    class SimpleCacheConfiguration {
      @Bean
      ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
              CacheManagerCustomizers cacheManagerCustomizers) {
          ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
          List<String> cacheNames = cacheProperties.getCacheNames();
          if (!cacheNames.isEmpty()) {
              cacheManager.setCacheNames(cacheNames);
          }
          return cacheManagerCustomizers.customize(cacheManager);
      }
    }
    ||
    \/  可以获取缓存中的东西
      public Cache getCache(String name) {
          Cache cache = this.cacheMap.get(name);
          if (cache == null && this.dynamic) {
              synchronized (this.cacheMap) {
                  cache = this.cacheMap.get(name);
                  if (cache == null) {
                      cache = createConcurrentMapCache(name);
                      this.cacheMap.put(name, cache);
                  }
              }
          }
          return cache;
      }
    保存缓存的数据结构是  线程安全
      private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);

    图片说明

运行流程:

@Cacheable:

  1. 方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
    (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。

  2. 去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
    key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
    SimpleKeyGenerator生成key的默认策略;
    如果没有参数;key=new SimpleKey();
    如果有一个参数:key=参数的值
    如果有多个参数:key=new SimpleKey(params);

  3. 没有查到缓存就调用目标方法;

  4. 将目标方法返回的结果,放进缓存中

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

@CachePut:即调用方法,更新缓存

  • 注意同步更新缓存
      /**
       * 修改了数据库的某个数据,同时更新缓存
       * 运行时机:
       *  1. 先调用用目标方法
       *  2. 将目标方法的结果缓存起来 对象需要设置key
       *    key是传入的Department对象 值,返回的Department对象
       *    department.id
       *  3. key="#result.id" 使用返回后的id @Cacheable是不能用result【顺序】
       * @param department
       * @return
       */
      @CachePut(value = "depa",key = "#department.id")
      public Department updateDepartment(Department department) {
          System.out.println("updateDepartment.....");
          departmentMapper.insertDepartment(department);
          return department;
      }

@CacheEvict缓存清除

  • 代码
      /**
       * 清除缓存
       * key指定要删除的数据key = "#id"
       * allEntries = true 删除所有的数据
       * beforeInvocation = false:缓存的清楚是否在方法之前执行
       * 默认代表是在方法执行之后执行【方法出错了 会影响是否清除】
       * @param id
       */
      @CacheEvict(value = "depa")
      public void deleteDepartment(Integer id) {
          System.out.println("updateDepartment.....");
      }

@Caching

  • 代码
      // @Caching 定义复杂的缓存规则
      @Caching(
              cacheable = {
                      @Cacheable(/*value="emp",*/key = "#lastName")
              },
              put = {
                      @CachePut(/*value="emp",*/key = "#result.id"),
                      @CachePut(/*value="emp",*/key = "#result.email")
              }
      )
      public Department department(Department department){
          return department;
      }

@ CacheConfig

  • 代码
    @CacheConfig(cacheNames="depa"/*,cacheManager = "DepartmenCacheManager"*/) //抽取缓存的公共配置
    @Service
    public class MyDepartmentDao {}

在工作用redis

整合redis作为缓存
步骤

  • 导入start

          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-data-redis</artifactId>
          </dependency>
  • 需要使用两个Bean

    public class RedisAutoConfiguration {
    
      @Bean
      @ConditionalOnMissingBean(name = "redisTemplate")
      public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
              throws UnknownHostException {//k-v操作的都是对象
          RedisTemplate<Object, Object> template = new RedisTemplate<>();
          template.setConnectionFactory(redisConnectionFactory);
          return template;
      }
    
      @Bean
      @ConditionalOnMissingBean
      public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
              throws UnknownHostException {//操作k-v都是字符串
          StringRedisTemplate template = new StringRedisTemplate();
          template.setConnectionFactory(redisConnectionFactory);
          return template;
      }
  • 用docker创建redis

    cznczai@cznczaihadoop101:~$ sudo docker run -itd --name redis-test -p 6379:6379 redis   :需要指定接口
    63f54ff1e789cfc595455d288323245cc5a314905981a79612a5db6867f72621
    cznczai@cznczaihadoop101:~$ sudo docker exec -it redis-test /bin/bash
  • 测试代码

    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class Test01 {
      @Autowired
      StringRedisTemplate stringRedisTemplate;//操作字符串的
      @Autowired
      RedisTemplate redisTemplate;//操作对象的
    
      @Test
      public void test01(){
          stringRedisTemplate.opsForValue().append("msg","hello");
          String msg = stringRedisTemplate.opsForValue().get("msg");
          System.out.println(msg);
      }
    }
  • 效果
    图片说明

  • 将数据转化为json 并修改转化器

    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class Test01 {
      @Autowired
      StringRedisTemplate stringRedisTemplate;//操作字符串的
      @Autowired
      RedisTemplate redisTemplate;//操作对象的
    
      @Test
      public void test01() {
          redisTemplate.setDefaultSerializer(new Jackson2JsonRedisSerializer<User>(User.class));
          User user = new User();
          redisTemplate.opsForValue().set("user",user);
          User user1 = (User) redisTemplate.opsForValue().get("user");
          System.out.println(user1.toString());//User{age=1}
      }
    }
    
    class User implements Serializable {
      public int age = 1 ;
      @Override
      public String toString() {
          return "User{" + "age=" + age + '}';
      }
    }
    
    //@Configuration
    //class RedisConfig {
    //    @Bean
    //    public RedisTemplate<Object, User> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    //        RedisTemplate<Object, User> template = new RedisTemplate<Object, User>();
    //        template.setConnectionFactory(redisConnectionFactory);
    //        template.setDefaultSerializer(new Jackson2JsonRedisSerializer<User>(User.class));
    //        return template;
    //    }
    //}
  • 配置是有顺序的 redis先于simple 所以用redis
    通过@ConditionalOnMissingBean(CacheManager.class)

  • 自定义CacheManager 可以通过下面的代码进行修改

      @Bean
      RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
              ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
              ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
              RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
          RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
                  determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
          List<String> cacheNames = cacheProperties.getCacheNames();
          if (!cacheNames.isEmpty()) {
              builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
          }
          redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
          return cacheManagerCustomizers.customize(builder.build());
      } 
  • Cacheable 也对redis生效了 也可以指定对应的Cache管理器

  • @Primary来指定默认的缓存管理器

全部评论

相关推荐

赏个offer求你了:友塔HR还专门加我告诉我初筛不通过😂
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务