Spring Cache简单使用

1、简介

  • Spring 从 3.1 开始定义了 org.springframework.cache.Cache
    和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术;并支持使用 JCache(JSR-107)注解简化我们开发;
  • Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现 ; 如 RedisCache ,EhCacheCache, ConcurrentMapCache 等;
  • 每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
  • 使用 Spring 缓存抽象时我们需要关注以下两点;
    • 1、确定方法需要被缓存以及他们的缓存策略
    • 2、从缓存中读取之前缓存存储的数据

2、基础概念

在这里插入图片描述

3、简单使用

3.1、引入依赖

        <!--引入redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <!--排除spring redis自己整合的lettuce-core-->
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        
        <!--上面排除了lettuce-core后 使用jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

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

3.2、在启动类上开启缓存

@SpringBootApplication
@MapperScan("com.qinjian.gulimall.product.dao")
@EnableCaching
public class GulimallProductApplication {

    public static void main(String[] args) {
        SpringApplication.run(GulimallProductApplication.class, args);
    }

}

3.3、在方法上使用注解

    @Cacheable(value = {"category"},key = "#root.method.name") // 使用SpEl表达式获取方法名
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        System.out.println("getLevel1Categorys......");
        long l = System.currentTimeMillis();
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return categoryEntities;
    }

4、进阶配置

SpringBoot自动配置了Spring Cache,在CacheAutoConfiguration中导入了RedisCacheConfiguration
在这里插入图片描述
在这里插入图片描述
RedisCacheConfiguration自动配置了RedisCacheManager,RedisCacheManager初始化所有的缓存,每个缓存决定使用什么配置,如果redisCacheConfiguration有就用已有的,没有就用默认配置,想改缓存的配置,就只需要给容器中放一个redisCacheConfiguration即可,就会应用到当前RedisCacheManager管理的所有缓存分区中
在这里插入图片描述
创建一个缓存配置类,自定义一个RedisCacheConfiguration

@Configuration
@EnableCaching // 把启动类中的该注解移到此处统一配置
@EnableConfigurationProperties(CacheProperties.class) // 加载原有的配置
public class MyCacheConfig {

    /**
     * 配置文件中的东西没有用上
     * 原来的CacheProperties
     *          @ConfigurationProperties(prefix = "spring.cache")
     *          public class CacheProperties{}
     *  要想让它生效,使用注解
     * @return
     */
    @Bean
    RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // 此处必须重新返回config对象,不然序列化设置将不生效
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        // 将配置文件中的所有配置也都生效
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixKeysWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }
        return config;
    }
}

yml配置cache

spring:
  redis:
    host: 192.168.200.139
    port: 6379
#    spring cache使用redis作为缓存
  cache:
#    设置缓存类型为redis
    type: redis
    redis:
#      设置缓存存活时间 单位是毫秒
      time-to-live: 3600000
#      设置缓存前缀 如果指定了前缀就用我们指定的前缀,如果没有就默认使用缓存的名字作为前缀
      key-prefix: CACHE_
#      设置是否使用缓存前缀,默认为true
      use-key-prefix: false
#      是否缓存空值,防止缓存穿透
      cache-null-values: true

5、注解

在这里插入图片描述
在这里插入图片描述

5.1、@Cacheable

@Cacheable(value = {“category”},key = “‘level1Categorys’”):(读模式)代表当前方法的结果需要缓存,如果缓存中有,方法不调用,如果缓存中没有,会调用方法,最后将方法的结果放入缓存

  • 1、value = {“category”}:每一个需要缓存的数据,都来指定要放到哪个名字的缓存。【缓存的分区(按照业务类型分)】
  • 2、默认行为
    • 1、如果缓存中有,方法不用调用
    • 2、key默认自动生成:缓存的名字category::SimpleKey [](自主生成的key值)
    • 3、缓存的value的值。默认使用jdk序列化机制,将序列化后的数据存到redis
    • 4、默认过期时间ttl -1;永久
  • 3、自定义:
	// @Cacheable(value = {"category"},key = "'level1Categorys'") // 指定key的字符串名称
    @Cacheable(value = {"category"},key = "#root.method.name") // 使用SpEl表达式获取方法名
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        System.out.println("getLevel1Categorys......");
        long l = System.currentTimeMillis();
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return categoryEntities;
    }

5.2、@CacheEvict

@CacheEvict(value = “category”,key = “‘getLevel1Categorys’”):缓存失效模式,执行完方法后,删除指定的缓存。

  • value: 指定分区名称
  • key:指定要删除的缓存名称
  • allEntries:是否要删除指定分区下的所有缓存
    // @CacheEvict(value = "category",key = "'getLevel1Categorys'")
    @CacheEvict(value = "category",allEntries = true)
    @Transactional
    @Override
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);
        if (!StringUtils.isEmpty(category.getName())) {
            categoryBrandRelationService.upDateCategory(category.getCatId(), category.getName());
            // todo 更新其他关联表
        }
        // 删除缓存
        // stringRedisTemplate.delete("catalogJsons");
    }

5.3、@Caching

@Caching:可以使用多个缓存注解组合使用

    @Caching(evict = {
             @CacheEvict(value = "category",key = "'getLevel1Categorys'"),
             @CacheEvict(value = "category",key = "'getCatalogJson'")
  	})
    @Transactional
    @Override
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);
        if (!StringUtils.isEmpty(category.getName())) {
            categoryBrandRelationService.upDateCategory(category.getCatId(), category.getName());
            // todo 更新其他关联表
        }
        // 删除缓存
        // stringRedisTemplate.delete("catalogJsons");
    }

5、表达式语法

在这里插入图片描述

6、原理与不足

  • 1、读模式:
    • 缓存穿透:查询一个null数据。解决:缓存空数据; yml配置 cache-null-values: true
    • 缓存击穿:大量并发进来同时查询一个正好过期的数据。解决:加锁,默认是无加锁的。
      @Cacheable中有一个sync=true的属性,设置为true时会加锁同步执行,解决击穿问题。
    • 缓存雪崩:缓存中大量的key同时过期。解决:加随机时间。加上过期时间yml配置 time-to-live: 3600000
  • 2、写模式:(保证缓存与数据库一致)
    • 1、读写加锁。(读多写少的业务数据)
    • 2、引入Canal,感知MySql的更新去更新数据库
    • 3、读多写多,直接取数据库查询就行

总结:
常规数据:(读多写少,即时性、一致性不高的数据),完全可以使用Spring-Cache。写模式(只要缓存的数据有过期时间就足够了)
特殊数据:特殊设计

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值