RedisTemplate直接调用multi()会抛UnsupportedOperationException,因其multi()为空实现;必须在SessionCallback内通过operations.multi()调用,才能绑定连接并启用事务队列。

RedisTemplate 本身不支持原生事务语义,必须用 SessionCallback 或 RedisOperations 手动封装 MULTI/EXEC 流程,否则调用 multi() 会直接抛 UnsupportedOperationException。
为什么直接调用 redisTemplate.multi() 会报错
因为 RedisTemplate 的 multi() 方法默认是空实现(委托给 RedisConnection,而 Lettuce/Jedis 的连接实例在非回调上下文中不支持交互式事务模式)。你看到的“开启事务”动作,只有在 SessionCallback 或 RedisCallback 内部执行时,才会真正绑定到当前连接并启用队列缓冲。
常见错误现象:java.lang.UnsupportedOperationException: RedisTemplate.multi() is not supported in standalone mode —— 这不是配置问题,是调用位置错了。
- 必须在
redisTemplate.execute(...)回调内调用operations.multi() - 不能在 service 方法体里直接写
redisTemplate.multi() -
opsForValue().set(...)等操作也必须通过operations实例调用,而非原始redisTemplate
SessionCallback 是唯一推荐的事务入口
它保证整个回调逻辑运行在同一个 RedisConnection 上,且自动处理连接获取/释放,避免手动管理资源。相比 RedisCallback,SessionCallback 还提供 RedisOperations 封装,能直接使用 opsForXxx(),写法更贴近日常开发习惯。
示例关键片段:
redisTemplate.execute(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
operations.multi(); // ✅ 正确:在 callback 内调用
operations.opsForValue().set("a", "1");
operations.opsForValue().set("b", "2");
operations.exec(); // 提交;若中途异常,需手动 discard
return null;
}
});
- 不要在
try外写exec(),否则异常时事务已提交 - 捕获异常后必须显式调用
operations.discard(),否则连接可能卡在 MULTI 状态 - 返回值由你自己决定,
exec()结果是List<object></object>,包含每条命令的响应
WATCH + SessionCallback 实现乐观锁
Redis 事务不自带行级锁,靠 WATCH 监视 key 变化来实现“检查-执行”原子性。这个操作只能在 SessionCallback 内完成,且必须在 multi() 之前调用。
典型场景:库存扣减防超卖
redisTemplate.execute(new SessionCallback<Boolean>() {
@Override
public Boolean execute(RedisOperations operations) throws DataAccessException {
operations.watch("stock:1001"); // ✅ 必须在 multi 前
String stock = operations.opsForValue().get("stock:1001");
if (Integer.parseInt(stock) > 0) {
operations.multi();
operations.opsForValue().decrement("stock:1001");
List<Object> result = operations.exec(); // 若 stock 被改,result == null
return result != null;
}
return false;
}
});
-
WATCH后如果其他客户端修改了被监视 key,exec()返回null,不是异常 - 务必检查
exec()返回值,null表示事务被放弃,需重试或降级 - 不能把
WATCH和业务逻辑(如查库存)拆到两个 callback 里,会丢失原子性
事务失败后没有回滚,只有丢弃
Redis 事务没有 RDBMS 那样的回滚机制。一旦 exec() 开始执行,队列中命令按序执行,某个命令失败(如对 string 执行 ZADD)不会中断后续命令,也不会撤销前面已成功的操作。
这意味着:
- 事务内应尽量避免混合数据类型操作,减少单点失败风险
- 不要依赖“全部成功或全部失败”,要为每个命令的失败单独处理(比如检查
decr()返回值是否为负) - 如果业务强依赖 ACID,Redis 事务不适合,考虑用 Lua 脚本或外部协调服务
最容易被忽略的一点:事务不是万能锁。WATCH 只对 key 生效,无法覆盖复杂条件判断;multi/exec 之间的时间窗口仍可能被并发写入绕过——真正安全的方案往往需要结合应用层重试+幂等设计。


















