You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot中Redis缓存过期时自动更新数据库表数据的实现方法

实现Redis缓存过期自动更新数据库日期字段的方案

针对你的需求,有两种主流实现方式,无需依赖业务方法调用就能自动检测缓存过期并触发数据库更新:

方案一:利用Redis键空间通知(实时触发)

Redis原生支持键空间通知,可以监听键的过期事件,一旦指定缓存键过期,立刻触发数据库更新操作。

步骤1:开启Redis键空间通知

修改Redis配置文件redis.conf,添加或修改以下配置:

notify-keyspace-events "Ex"
  • E表示开启键事件通知,x表示监听过期事件。
  • 如果无法修改配置文件,也可以通过Redis命令临时开启(重启后失效):
127.0.0.1:6379> config set notify-keyspace-events Ex
127.0.0.1:6379> config save  # 持久化配置到文件

步骤2:Spring Boot中配置监听过期事件

配置Redis消息监听容器

@Configuration
public class RedisExpireListenerConfig {

    @Bean
    RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory,
                                                               EnterpriseCacheExpireListener expireListener) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 监听db 0的过期事件,替换为你的Redis数据库编号
        PatternTopic expireTopic = new PatternTopic("__keyevent@0__:expired");
        container.addMessageListener(expireListener, expireTopic);
        return container;
    }
}

编写过期事件监听器

@Component
public class EnterpriseCacheExpireListener implements MessageListener {

    private final EnterpriseAccountService enterpriseAccountService;

    // 构造注入业务服务类
    public EnterpriseCacheExpireListener(EnterpriseAccountService enterpriseAccountService) {
        this.enterpriseAccountService = enterpriseAccountService;
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        String expiredKey = new String(message.getBody());
        // 过滤出企业账户相关的缓存键(需保证键名有统一前缀,比如enterprise:account:123)
        if (expiredKey.startsWith("enterprise:account:")) {
            // 从键名中提取企业账户ID
            Long accountId = Long.parseLong(expiredKey.split(":")[2]);
            // 执行数据库日期字段更新
            enterpriseAccountService.updateLastAccessDate(accountId);
        }
    }
}

方案二:定时任务扫描(延迟触发)

如果不想修改Redis配置,可以用Spring Schedule定时扫描指定前缀的缓存键,检查过期状态并更新数据库。

编写定时任务类

@Component
@EnableScheduling
public class CacheExpireCheckTask {

    private final StringRedisTemplate stringRedisTemplate;
    private final EnterpriseAccountService enterpriseAccountService;

    public CacheExpireCheckTask(StringRedisTemplate stringRedisTemplate,
                                EnterpriseAccountService enterpriseAccountService) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.enterpriseAccountService = enterpriseAccountService;
    }

    // 每5分钟扫描一次,可根据需求调整频率
    @Scheduled(fixedRate = 300000)
    public void scanExpiredCaches() {
        // 用SCAN命令扫描指定前缀的键,避免KEYS命令阻塞Redis
        ScanOptions scanOptions = ScanOptions.scanOptions()
                .match("enterprise:account:*")  // 匹配企业账户缓存键前缀
                .count(100)  // 每次扫描100条,可调整
                .build();

        Cursor<String> cursor = stringRedisTemplate.executeWithStickyConnection(connection -> connection.scan(scanOptions));
        while (cursor.hasNext()) {
            String key = cursor.next();
            // 获取键剩余过期时间:-1=永久有效,-2=已过期
            Long remainTime = stringRedisTemplate.getExpire(key);
            if (remainTime <= 0) {
                Long accountId = Long.parseLong(key.split(":")[2]);
                enterpriseAccountService.updateLastAccessDate(accountId);
            }
        }

        // 关闭游标
        try {
            cursor.close();
        } catch (IOException e) {
            // 处理游标关闭异常
            e.printStackTrace();
        }
    }
}

关键注意事项

  • 缓存键命名规范:必须给企业账户缓存键设置统一前缀(如enterprise:account:{id}),避免误处理其他缓存键的过期事件。
  • 幂等性保证:数据库更新操作要保证幂等,比如用UPDATE enterprise_account SET last_access_date = NOW() WHERE id = ?,即使多次执行也不会产生问题。
  • 性能考量:定时任务方案中必须用SCAN而非KEYS命令,避免阻塞Redis;键空间通知方案要注意Redis集群场景下,需确保每个节点都开启通知,且监听器能接收所有节点的事件。

内容的提问来源于stack exchange,提问作者Sai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 23:10:45