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
相关产品推荐
相关产品推荐

