Spring Boot集成Redis缓存


所谓缓存,即 Redis 会把 MySQL 中经常被查询的数据缓存起来,这样当用户来访问的时候,就不需要到 MySQL 中去查询了,而是直接获取 Redis 中的缓存数据,从而降低了后端数据库的读取压力。

如果说用户查询的数据 Redis 没有,此时用户的查询请求就会转到 MySQL 数据库,当 MySQL 将数据返回给客户端时,同时会将数据缓存到 Redis 中,这样用户再次读取时,就可以直接从 Redis 中获取数据。

明白了实现原理,下面就介绍如何在 Spring Boot 项目中配置 Redis 缓存。

一、注解介绍

1. 作用描述

在具体上手之前,我们先了解一下 Spring Boot 中缓存常用的注解。

注解 作用
@CacheConfig 作用于实现缓存方法的类上,通过 cacheNames 设置统一前缀。
@Cacheable 执行方法前会根据 Key 读取缓存,若存在则返回缓存并跳过方法执行,否则继续执行方法并将结果存入缓存。
@CachePut 将每次方法返回值存入缓存中,但与 @Cacheable 不同是其在方法执行之前不会读取缓存。
@CacheEvict 根据 Key 删除对应缓存,通常在对数据变更之后需要更新缓存。
@Caching 通过 @Caching 可实现多种缓存嵌套,如同时进行缓存写入和删除。

2. 示例介绍

在上面表格中详细介绍了几类常见的注解与其对应的作用,下面通过具体的示例加深了解。

@CacheConfig(cacheNames = "users")
public class UserServiceImpl implements UserService {
  
    @Cacheable(key = "#id")
    public User get(String id) {
        return userDao.query(id);
    }

    @CachePut(key = "#id")
    public User query(String id) {
        return userDao.query(id);
    }

    @CacheEvict(key = "#user.id")
    public User update(User user) {
        return userDao.update(user);
    }
    
    @Caching(
        cacheable = {
            @Cacheable(key = "'list'"),
            @Cacheable(key = "#user.id")
        },
        evict = {
            @CacheEvict(key = "'list'"),
            @CacheEvict(key = "#user.id")
    })
    public User insert(User user) {
        return userDao.insert(user);
    }
}

二、项目配置

了解了缓存注解的基本使用,下面就介绍在现实业务场景中如何进行使用。

1. 项目创建

Spring Boot项目创建步骤之前文章已经详细讲过了,这里不再重复介绍,之后文章将基于之前的工程进行改造,具体步骤参考之前文章:Spring boot实现接口开发

创建完项目后在 Maven 工程中引入 Redis 相关依赖。

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

2. 文件配置

application.yml文件中新增 Redis 相关配置,其中password是可选的,如果你没有设置密码就注释该行。

spring:
    redis:
    host: localhost
    port: 6379
    password: 123456
    lettuce:
        pool:
            max-idle: 8
            max-active: 8
            max-wait: -1ms
            min-idle: 0
            shutdown-timeout: 100ms

3. Redis配置

通过自定义配置对 Redis 缓存过期时间、键值对序列化进行设置,新建RedisConfig文件,其中keyvalues序列化使存入缓存的数据以可读的方式存储。

注意下面两点:

  • 如果设置了 disableCachingNullValues() 禁止缓存空值,在使用@Cacheable时需要添加unless="#result == null",否则当查询到空值时程序将会出现异常。

  • 如果不设置禁止空值,即空值也进行存储,虽然不会对数据库产生性能损耗,但会浪费Redis缓存空间。因此需要根据具体情况进行判断选择。

@Configuration
public class RedisConfig {

    /**
      * 配置缓存管理器
      */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
            // 设置缓存前缀
            .prefixCacheNameWith("healthcare:")
            // 设置缓存过期时间
            .entryTtl(Duration.ofMinutes(5))
            // 设置 key 序列化
            .serializeKeysWith(keyPair())
            // 设置 value 序列化
            .serializeValuesWith(valuePair());

        Map<String, RedisCacheConfiguration> redisMap = new HashMap<>();
        redisMap.put("users", redisCacheConfiguration);

        // 返回 Redis 缓存管理器
        return RedisCacheManager.builder(factory)
            .withInitialCacheConfigurations(redisMap)
            .build();
    }

    /**
      * 配置 键 序列化
      */
    private RedisSerializationContext.SerializationPair<String> keyPair() {
        return RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
    }

    /**
     * 配置 值 序列化
     *
     * 使用 GenericJackson2JsonRedisSerializer 替换默认序列化
     */
    private RedisSerializationContext.SerializationPair<Object> valuePair() {
        return RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
    }
}

三、业务实操

1. 开启缓存

在启动类上添加 @EnableCaching 开启注解。

@EnableCaching
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

Service 实现类及方法上添加 @CacheConfig 并配置缓存前缀,注意要和上面配置类中设置的一致。

@Service
@CacheConfig(cacheNames = "users")
public class UserServicesImpl implements UserServices {
    // do something

}

在需要开启缓存的方法前添加 @Cacheable 注解并设置相关参数。

  • 其中 key = "#ID" 表示用传入的 ID 作为缓存的键值,如传入 123 则在 Redis 中存储路径为 users:123
  • 如果在配置文件中设置了禁止空值,需要在 @Cacheable 中添加 unless="#result == null",否则当查询到空值时程序将会出现异常。
@Service
@CacheConfig(cacheNames = "users")
public class UserServicesImpl implements UserServices {
    @Cacheable(key = "#ID")
    public User get(String ID) {
        return userMapper.get(ID);
    }

    @Cacheable(key = "#user.ID")
    public int update(User user) {
        return userMapper.update(user);
    }
        
    @Cacheable(key = "#ID", unless="#result == null")
    public User get(String ID) {
        return userMapper.get(ID);
    }
}

此时通过Postman调用接口发送重复请求后端查询语句只会执行一次,而不会像之前重复的请求数据库,从而实现性能优化。

2. 缓存删除

我们先看一下它是如何将数据存入缓存的,通过 @Cacheable 注解在获取数据之后通过配置的KEY值将查询的数据以键值对的形式存入缓存。

@Cacheable(key = "'list'")
public List<User> list() {
    return userMapper.list();
}

那么问题就简单了,若要解决数据不一致的情况,只需在更新操作的时候同时将缓存进行删除。当下次再发送请求,由于缓存中没有所需数据,将会重新转到后端数据库请求,此时查询到的数据即为最新数据。

实现起来也很简单,通过 @CacheEvict 注解配置对应的key即可。

@CacheEvict(key = "'list'")
public int add(User user) {
    return userMapper.insert(user);
}

3. 多缓存删除

当删除单个缓存值时通过 @CacheEvict 注解可以直接删除,但如果删除多个缓存时就需要使用 @Caching 进行缓存清除了。

举个简单例子,比如用户表一共有两条数据,项目在针对获取单个用户数据和全部表中数据都做了缓存处理,代码如下:

@Cacheable(key = "'list'")
public List<User> list() {
    return userMapper.list();
}
        
@Cacheable(key = "#user.id")
public User getUser(User user) {
    return userMapper.getUser(user);
}

此时对表中 id = 123 的数据进行了更新,显然缓存中的 users:listusers:123 数据和真实数据已经不一致了,但 @CacheEvict 只能删除单条缓存。

这时候我们就需要@Caching组合多个注解进行缓存删除,代码实现如下:

@Caching(evict = {
        @CacheEvict(key = "'list'"),
        @CacheEvict(key = "#user.id")
    }
)
public int update(User user) {
    return userMapper.update(user);
}

还有一种更粗暴的方式,只要数据发生变更就直接清空所有缓存,但实际开发中用到相对较少,因为会将正常的缓存一同清空。

@CacheEvict(allEntries = true)
public int update(User user) {
    return userMsapper.update(user);
}

示例源码:Redis缓存配置


文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录