什么是Spring Cache?Spring项目如何使用?

前言

目前Spring Cloud微服务在Web项目中占据了主流地位,如果去面试关于Spring Cloud的岗位时,面试官一般都会提问你的项目是如何优化的,从哪些方面入手去优化。而缓存技术绝对是项目必不可少的,所以我们必须掌握好Java项目的缓存技术。

目前,在 Java 中,常见的缓存有以下几种:

  1. JVM 缓存:JVM 缓存是指 Java 虚拟机为类加载器所创建的缓存。Java 虚拟机通过缓存已经加载的类来提高应用程序的性能。
  2. 数据库缓存:数据库缓存是指将查询结果缓存在内存中,以便下次查询时可以直接从内存中获取,避免了重复查询数据库的操作。
  3. Web 缓存:Web 缓存是指将 Web 页面、图片等静态资源缓存到本地,以便下次访问时可以直接从本地获取,提高 Web 应用程序的性能。
  4. 分布式缓存:分布式缓存是指将数据缓存到多个节点上,以便多个应用程序可以共享缓存,提高系统的性能和可伸缩性。
  5. 本地缓存:本地缓存是指将数据存储在应用程序本地的内存或磁盘上,以便快速读取和更新数据,避免了频繁访问远程资源的开销。
  6. Spring 缓存:Spring 框架提供了一套缓存框架,可以将方法调用的结果缓存到内存或磁盘中,以便下次调用时可以直接从缓存中获取结果,提高应用程序的性能。

今天我主要讲解Spring 缓存,因为Spring cache简单、方便、效率高,绝对是Spring Cloud微服务项目的最佳选择

一、什么是Spring Cache

Spring Cache是Spring框架中的一个模块,它提供了一种简单的方法来缓存数据和对象。通过使用Spring Cache,开发人员可以将常用的数据和对象存储在内存中,以提高应用程序的性能和响应速度。

Spring Cache支持多种缓存实现,包括内存、Redis、Memcached等。开发人员可以根据自己的需求选择合适的缓存实现。

Spring Cache还提供了一些高级功能,如缓存预热、缓存过期、分布式缓存等,使得开发人员可以更加灵活地使用缓存。同时,Spring Cache还与Spring框架的其他模块集成良好,可以方便地与其他组件一起使用。

1、优点

  • 提高性能:通过将常用的数据和对象存储在内存中,Spring Cache可以大大提高应用程序的性能和响应速度。

  • 简化代码:Spring Cache提供了一些高级功能,如缓存预热、缓存过期和分布式缓存等,使得开发人员可以更加灵活地使用缓存,从而简化了代码。

  • 促进团队协作:Spring Cache可以促进团队协作,因为它可以在多个开发人员之间共享缓存数据,从而减少了重复的工作和代码冗余。

  • 实现分布式系统:Spring Cache可以与Spring框架的其他模块集成良好,可以方便地与其他组件一起使用,从而实现分布式系统。

  • 支持多种缓存实现:Spring Cache支持多种缓存实现,包括内存、Redis、Memcached等,开发人员可以根据自己的需求选择合适的缓存实现。

  • 提供基本的Cache抽象:方便切换各种底层Cache,通过注解Cache可以实现类似于事务一样,缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成。

  • 提供事务回滚时也自动回滚缓存:支持比较复杂的缓存逻辑。

  • 通过注解Cache可以实现类似于事务一样,缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成。

2、缺点

在使用 Spring Cache 时,可能会遇到一些不足之处。下面是一些可能的不足:

  • 读模式下缓存失效的问题:在 Spring Cache 中,缓存的读模式下可能会出现缓存失效的问题。这是因为在读模式下,缓存的数据可能会过期,导致缓存失效。为了解决这个问题,可以使用 Spring Cache 提供的读写分离功能,将读请求和写请求分别发送到不同的 Cache 实例。
  • 缓存穿透:在某些情况下,缓存穿透可能会发生。这是因为在读模式下,缓存的数据可能会被其他组件重新读取,导致缓存穿透。为了避免缓存穿透,可以使用 Spring Cache 提供的写模式,并设置 cache-null-values: true 来允许写入空值。
  • 缓存击穿:在大量并发请求的情况下,可能会出现缓存击穿的问题。这是因为在这种情况下,同时有大量的请求查询一个正好过期的数据,导致请求全部转发到数据库,从而导致数据库压力过大。为了避免缓存击穿,可以设置缓存的过期时间,或者使用其它缓存框架,如 Ehcache 或 RedisCache。
  • 缓存雪崩:在某些情况下,缓存的大量键过期时间相同,导致某一刻缓存同时失效,请求全部转发到数据库。为了避免缓存雪崩,可以使用 Spring Cache 提供的键值哈希策略,将键值哈希到不同的桶中,避免键值过期时间相同的问题。
  • 总的来说,Spring Cache 在处理读模式下缓存失效、缓存穿透、缓存击穿和缓存雪崩等问题方面表现良好,但在写模式下需要使用其他方式进行处理。使用 Spring Cache 可以轻松地管理和优化应用程序的缓存。

二、Spring Cache原理

1、基本流程图

在这里插入图片描述

2、原理

当我们在Spring中使用缓存时,会按照以下步骤进行操作:

  • 首先,Spring会检查是否使用了缓存注解。如果使用了,那么会在缓存的缓存管理器中查找是否有已经缓存的数据。

  • 如果缓存管理器中没有对应的缓存数据,那么会执行方法体中的代码,并将结果缓存起来。

  • 如果缓存管理器中已经存在缓存数据,那么直接返回缓存数据。

当我们在更新数据时,会按照以下步骤进行操作:

  • 首先,更新方法会执行,然后会更新数据库中的数据。

  • 然后,Spring会在缓存管理器中查找是否存在已经缓存的数据。

  • 如果存在缓存数据,那么会将缓存数据删除。

  • 下一次查询时,会重新执行方法体中的代码,并将新的结果缓存起来。

以上就是Spring Cache的工作原理。

三、Spring Cache具体实现与应用

1、引入SpringCache依赖

  • .引入SpringCache依赖
<!--Spring Cache,使用注解简化开发-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

-.引入redis依赖

<!--redis启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、属性配置

spring:
  redis:
    host: 192.168.56.10
    port: 6379
  cache:
    type: redis # 使用redis作为缓存
    redis:
      time-to-live: 3600s # 过期时间
      # key-prefix: CACHE_ # 会导致自己在@Cacheable里设置的名字失效,所以这里不指定
      use-key-prefix: true # key值加前缀
      cache-null-values: true # 缓存控制

3、类配置

@EnableConfigurationProperties(CacheProperties.class)
@EnableCaching
@Configuration
public class MyCacheConfig {
    
    

//    @Autowired
//    CacheProperties cacheProperties;

    /**
     * 需要将配置文件中的配置设置上
     * 1、使配置类生效
     * 1)开启配置类与属性绑定功能EnableConfigurationProperties
     *
     * @ConfigurationProperties(prefix = "spring.cache")  public class CacheProperties
     * 2)注入就可以使用了
     * @Autowired CacheProperties cacheProperties;
     * 3)直接在方法参数上加入属性参数redisCacheConfiguration(CacheProperties redisProperties)
     * 自动从IOC容器中找
     * <p>
     * 2、给config设置上
     */
    @Bean
    RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
    
    
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();

        //序列化key,不变
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        //序列化值,使用json
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        // 当自己往IOC注入了RedisCacheConfiguration配置类时,以下参数全都失效,需要手动设置
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        if (redisProperties.getTimeToLive() != null) {
    
    
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix() != null) {
    
    
            config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
    
    
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
    
    
            config = config.disableKeyPrefix();
        }
        return config;
    }
}

4、查询时读取缓存

 @Cacheable(value = {
    
    "category"}, key = "#root.method.name", sync = true)
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
    
    
        System.out.println("调用了getLevel1Categorys...");
        // 查询父id=0
        return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
    }

5、更新时读取缓存

@CacheEvict(value = {
    
    "category"}, allEntries = true) //删除category分区里所有的缓存
    @Transactional
    @Override
    public void updateCascade(CategoryEntity category) {
    
    
        this.updateById(category);
        if (!StringUtils.isEmpty(category.getName())) {
    
    
            // 更新冗余表
            categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
            // TODO 更新其他冗余表
        }       
    }

6、双写模式

/**
 * 级联更新
 * 缓存策略:双写模式,方法执行完更新缓存
 */
@CachePut(value = "category", key = "'level1Categorys'")
@Transactional
@Override
public void updateCascade(CategoryEntity category) {
    
    
    this.updateById(category);
    if (!StringUtils.isEmpty(category.getName())) {
    
    
        // 更新冗余表
        categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
        // TODO 更新其他冗余表
    }
}

7、注解解释:

@Cacheable:更新缓存【读操作:如果当前缓存存在方法不被执行,不存在则执行get方法并更新缓存】
@CacheEvict:删除缓存【写操作:失效模式,方法执行完删除缓存】
@CachePut:更新缓存【写操作:双写模式,方法执行完更新缓存】
@Caching:组合以上多个缓存操作
@CacheConfig:在类级别共享缓存的相同配置

源码下载:
gitee.com/charlinchenlin/koo-erp

猜你喜欢

转载自blog.csdn.net/lovoo/article/details/130740022
今日推荐