如何用Spring Cache实现对Redis的抽象

我们在进行Java项目开发时候,经常会用到Redis缓存例如数据库里的一些信息、手机验证码之类的,正常写法就会像去连mysql一样,这种硬编码的方式肯定是非常不合适的。

复制代码
  @Autowire
  private UserMapper userMapper;
  @Autowire
  private StringCommand stringCommand;
  //查询用户
  public User getUserById(Long userId) {
   String cacheKey = "userId_" + userId;
   User user=stringCommand.get(cacheKey);
   if(user != null) {
    return user;
   }
   user = userMapper.getUserById(userId);
   if(user != null) {
    stringCommand.set(cacheKey,user);
    return user;
   }
   //修改用户
   public void updateUser(User user){
    userMapper.updateUser(user);
    String cacheKey = "userId_" + userId.getId();
    stringCommand.set(cacheKey , user);
   }
   //删除用户
   public void deleteUserById(Long userId){
     userMapper.deleteUserById(userId);
     String cacheKey = "userId_" + userId.getId();
     stringCommand.del(cacheKey);
   }
  }

有没有啥更抽象、更优雅的方式来实现呢?

有!那就是使用Spring Cache,它是一个对缓存的抽象,即使使用的缓存不是Redis也可以。

我们看到这里最重要的是这个CacheManager,是他给我们提供了一个抽象,让我们不用关心底层缓存技术(如 Redis、Ehcache、Caffeine)

那如何使用呢?

当然是通过Spring中最常见的方式------注释了 !!!这也是CacheManager提供给我们的功能

在pom文件下引入对应包(spring-boot-starter-data-redis、spring-boot-starter-cache)后,在启动类上加@EnableCaching注释,就可以使用Spring Cache的功能了。

这里再解释一下注释里的value和key分别是啥:

  • 首先value指定的是缓存的名称,对应底层 Redis 的 key 前缀或 hash 名,也就是你缓存数据时存在哪个"区域"。比如下面这个示例,缓存内容都是存入名为 userCache 的缓存区。

  • key则是指定缓存项在缓存区中的唯一标识。如果不指定,Spring 会默认根据方法所有参数自动生成一个 key。

    // 先查缓存,有则返回缓存内容;如果没有,执行方法,并把返回值放进缓存。
    @Cacheable(value = "userCache", key = "#id")
    public User getUserById(Long id) {
    // 方法体只会在缓存未命中时执行
    return userService.getUserFromDB(id);
    }

    // 还有一种写法,假设查询对应id用户为空时,这样可以让查询结果不缓存
    // unless表示条件成立时,不缓存,也就是说:如果方法返回结果是 null,则不缓存
    @Cacheable(value = "userCache", key = "#id", unless = "#result == null")
    public User getUserById(Long id) {
    // 方法体只会在缓存未命中时执行
    return userService.getUserFromDB(id);
    }

    // 不查缓存,始终执行方法,然后将方法返回值更新到缓存中。
    @CachePut(value = "userCache", key = "#user.id")
    public User updateUser(User user) {
    // 更新数据库
    return userRepository.save(user);
    }

    // 删除缓存中的数据,可以指定 key,也可以一次清空整个缓存。
    @CacheEvict(value = "userCache", key = "#id")
    // @CacheEvict(value = "userCache", allEntries = true) 这个是全部清空
    public void deleteUser(Long id) {
    // 删除数据库中的数据
    userRepository.deleteById(id);
    }

还需要强调的一点,可能你会知道RedisTemplate,在我另一篇文章也讲了,这是一种给你手动操作Redis数据的方式,你可以调用.opsForXXX()来获取数据,是一种更加灵活、精细操作Redis的方式,例如在实现分布式锁等场景可以采用。但我们本文介绍的CacheManager是通过注释的方式自动操作缓存的,是一种方法级的自动缓存,简洁优雅,推荐用于 Controller/Service 层。

来个更加易懂的方式来解释,RedisTemplate像是你手动操作数据库的 JDBC 接口,而CacheManager则像是用 ORM 框架(比如 MyBatis)配置好后,通过注解自动执行数据库操作。

复制代码
@Configuration
@EnableCaching // 开启Spring Cache注解支持,这样的话就可以直接使用@@Cacheable、@CachePut、@CacheEvict等注释来用了,当然如果你已经在主程序上标了,这里就不用写了,@EnableCaching全局写一次就行
public class RedisConfig {

    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory factory, CacheTtlProperties ttlProps) {

        // 创建默认缓存配置:设置序列化方式、key前缀、是否缓存null值、默认过期时间等
        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) // key 使用字符串序列化
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); // value 使用 JSON 序列化

        // 创建一个 map 存放不同缓存区域(cacheName)对应的 TTL(过期时间)配置
        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();

        // 从application.yml配置中读取每个缓存区域自定义的 TTL,并生成对应的配置项
        for (Map.Entry<String, Long> entry : ttlProps.getTtl().entrySet()) {
            cacheConfigurations.put(entry.getKey(),
                defaultConfig.entryTtl(Duration.ofMillis(entry.getValue()))); // 将该 cacheName 设置为自定义 TTL
        }

        // 构建 RedisCacheManager:指定默认配置及每个缓存区域的个性化配置
        return RedisCacheManager.builder(factory)
            .cacheDefaults(defaultConfig) // 设置默认缓存行为
            .withInitialCacheConfigurations(cacheConfigurations) // 设置个性化缓存行为
            .build(); // 构建缓存管理器
    }
}

然后记得在application.yml配置对应参数,例如过期时间等:

复制代码
spring:
  redis:
    host: localhost # Redis 服务地址
    port: 6379       # Redis 端口号

  cache:
    type: redis      # 使用 Redis 作为缓存实现
    redis:
      time-to-live: 1800000 # 全局默认缓存过期时间(单位:毫秒,1800000ms = 30分钟)
      cache-null-values: false # 是否缓存 null,false 表示不缓存 null 值,假设想在每个地方自行判断要不要缓存null可以不写这行
      use-key-prefix: true     # 是否启用 key 前缀
      key-prefix: cache::      # 缓存 key 的统一前缀,也就是key都变成了cache::xxx
// 上面配置的是全局缓存过期时间,你如果还想单独指定redis缓存空间例如userCache、orderCache的过期时间可以写在下面,其余的空间仍然是30分钟
custom:
  cache:
    ttl:
      userCache: 600000     # userCache 缓存区域的 TTL(单位:毫秒,600000ms = 10分钟)
      orderCache: 3600000   # orderCache 缓存区域的 TTL(单位:毫秒,3600000ms = 1小时)
相关推荐
老神在在0012 小时前
javaEE1
java·开发语言·学习·java-ee
魔道不误砍柴功2 小时前
《接口和抽象类到底怎么选?设计原则与经典误区解析》
java·开发语言
small_white_robot3 小时前
Tomcat- AJP协议文件读取/命令执行漏洞(幽灵猫复现)详细步骤
java·linux·网络·安全·web安全·网络安全·tomcat
图梓灵3 小时前
Maven与Spring核心技术解析:构建管理、依赖注入与应用实践
java·笔记·spring·maven
pengles3 小时前
Spring AI 多模型智能协作工作流实现指南
spring·chatgpt
岁忧4 小时前
(nice!!!)(LeetCode 每日一题) 3372. 连接两棵树后最大目标节点数目 I (贪心+深度优先搜索dfs)
java·c++·算法·leetcode·go·深度优先
加什么瓦4 小时前
Java—多线程
java·开发语言
野木香4 小时前
idea使用笔记
java·笔记·intellij-idea
bing_1585 小时前
在 Spring Boot 项目中如何合理使用懒加载?
java·spring boot·后端
CN.LG5 小时前
MyBatis 的动态 SQL 特性来实现有值字段才进行插入或更新
java·sql·mybatis