Redis 事务在 SpringBoot 中的应用 (io.lettuce.core.RedisCommandExecutionException: ERR EXEC without MULTI)
我们在 SpringBoot 中使用 Redis 时,会引入如下的 redis starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
这个 starter 引入了 jedis 和 spring-data-redis 两个与 redis 核心的包。
Redis 事务相关的命令参考
Redis 事务在 SpringBoot 中的应用
说明:下面以测试用例的形式说明 Redis 事务在 SpringBoot 中正确与错误的用法。首先,看一看当前测试用例的主体代码:
package com.imooc.ad.service;
import com.imooc.ad.Application;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
/**
* <h1>Redis 事务测试</h1>
* Created by Qinyi.
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class RedisTransTest {
/** 注入 StringRedisTemplate, 使用默认配置 */
@Autowired
private StringRedisTemplate stringRedisTemplate;
- 错误的用法
/**
* <h2>没有开启事务支持: 事务执行会失败</h2>
* */
@Test
public void testMultiFailure() {
stringRedisTemplate.multi();
stringRedisTemplate.opsForValue().set("name", "qinyi");
stringRedisTemplate.opsForValue().set("gender", "male");
stringRedisTemplate.opsForValue().set("age", "19");
System.out.println(stringRedisTemplate.exec());
}
执行以上测试用例,会抛出如下的异常信息:
Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR EXEC without MULTI
这里给出的错误信息显示:在执行 EXEC 命令之前,没有执行 MULTI 命令。这很奇怪,我们明明在测试方法的第一句就执行了 MULTI。通过追踪 multi、exec 等方法,我们可以看到如下的执行源码(spring-data-redis):
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
Assert.notNull(action, "Callback object must not be null");
RedisConnectionFactory factory = getRequiredConnectionFactory();
RedisConnection conn = null;
try {
// RedisTemplate 的 enableTransactionSupport 属性标识是否开启了事务支持,默认是 false
if (enableTransactionSupport) {
// only bind resources in case of potential transaction synchronization
conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
} else {
conn = RedisConnectionUtils.getConnection(factory);
}
boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);
源码中已经给出了答案:由于 enableTransactionSupport 属性的默认值是 false,导致了每一个 RedisConnection 都是重新获取的。所以,我们刚刚执行的 MULTI 和 EXEC 这两个命令不在同一个 Connection 中。
- 设置 enableTransactionSupport 开启事务支持
解决上述示例的问题,最简单的办法就是让 RedisTemplate 开启事务支持,即设置 enableTransactionSupport 为 true 就可以了。测试代码如下:
/**
* <h2>开启事务支持: 成功执行事务</h2>
* */
@Test
public void testMultiSuccess() {
// 开启事务支持,在同一个 Connection 中执行命令
stringRedisTemplate.setEnableTransactionSupport(true);
stringRedisTemplate.multi();
stringRedisTemplate.opsForValue().set("name", "qinyi");
stringRedisTemplate.opsForValue().set("gender", "male");
stringRedisTemplate.opsForValue().set("age", "19");
System.out.println(stringRedisTemplate.exec()); // [true, true, true]
}
- 通过 SessionCallback,保证所有的操作都在同一个 Session 中完成
更常见的写法仍是采用 RedisTemplate 的默认配置,即不开启事务支持。但是,我们可以通过使用 SessionCallback,该接口保证其内部所有操作都是在同一个Session中。测试代码如下:
/**
* <h2>使用 SessionCallback, 在同一个 Redis Connection 中执行事务: 成功执行事务</h2>
* */
@Test
@SuppressWarnings("all")
public void testSessionCallback() {
SessionCallback<Object> callback = new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForValue().set("name", "qinyi");
operations.opsForValue().set("gender", "male");
operations.opsForValue().set("age", "19");
return operations.exec();
}
};
// [true, true, true]
System.out.println(stringRedisTemplate.execute(callback));
}
总结:我们在 SpringBoot 中操作 Redis 时,使用 RedisTemplate 的默认配置已经能够满足大部分的场景了。如果要执行事务操作,使用 SessionCallback 是比较好,也是比较常用的选择。
原文链接:http://www.imooc.com/article/281310?block_id=tuijian_wz#
Redis 事务在 SpringBoot 中的应用 (io.lettuce.core.RedisCommandExecutionException: ERR EXEC without MULTI)的更多相关文章
- Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR invalid longitude,latitude pair 111.110000,111.230000
io.lettuce.core.RedisCommandExecutionException: ERR invalid longitude,latitude pair 111.110000,111.2 ...
- io.lettuce.core.RedisCommandExecutionException: ERR unknown command 'GEOADD'
io.lettuce.core.RedisCommandExecutionException: ERR unknown command 'GEOADD' at io.lettuce.core.Exce ...
- MySQL事务在MGR中的漫游记—路线图
欢迎访问网易云社区,了解更多网易技术产品运营经验. MGR即MySQL Group Replication,是MySQL官方推出的基于Paxos一致性协议的数据高可靠.服务高可用方案.MGR在20 ...
- springboot2集成redis5报错:io.lettuce.core.RedisException: io.lettuce.core.RedisConnectionException: DENIED Redis is running in protected
报错信息如下: Caused by: io.lettuce.core.RedisException: io.lettuce.core.RedisConnectionException: DENIED ...
- io.lettuce.core.protocol.ConnectionWatchdog - Reconnecting, last destination was ***
一.问题 redis起来后一直有重连的日志,如下图: 二.分析 参考lettuce-core的github上Issues解答https://github.com/lettuce-io/lettuce- ...
- springboot连接redis错误 io.lettuce.core.RedisCommandTimeoutException:
springboot连接redis报错 超时连接不上 可以从以下方面排查 1查看自己的配置文件信息,把超时时间不要设置0毫秒 设置5000毫秒 2redis服务长时间不连接就会休眠,也会连接不上 重 ...
- Mongodb 的事务在python中的操作
代码实现如下: import pymongo mgClient = pymongo.MongoClient("ip", "port") session = mg ...
- Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to ;XX.XX.XX.XX:6379] with root cause
java.net.ConnectException: Connection refused: no further information at sun.nio.ch.SocketChannelImp ...
- redis问题解决 Caused by: io.lettuce.core.RedisException: io.lettuce.core.RedisConnectionException: DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specifie
1找到redis的配置文件 redis.conf vim redis.conf 修改 protected-mode yes 改为 protected-mode no 注释掉 #bin 127.0.0 ...
随机推荐
- List 重载添加-add,删除-remove方法,以及获取子集方法
package seday12; import java.util.ArrayList;import java.util.List; /*** @author xingsir* List重载了一对ad ...
- 8.5-Day1T3--Asm.Def 的一秒
题目大意 略... (吐槽这题面...让我毫无阅读兴趣) 题解 首先要求出在以两条斜线为新坐标轴下,每个点的坐标 那么....按x先排序 再求y的最长上升子序列 复杂度O(nlogn)吧 记得开lon ...
- 记录STM32调试
问题:加入红外初始化后,程序卡在红外初始化处 解决思路: 1.确认时钟是不是好的 2.把定时器分解调试(输入捕获.溢出分开一步一步弄) 已解决:定时器的溢出中断 注意:STM32Cube配置好后,需要 ...
- 吴裕雄 PYTHON 神经网络——TENSORFLOW 双隐藏层自编码器设计处理MNIST手写数字数据集并使用TENSORBORD描绘神经网络数据2
import os import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data os.envi ...
- HeroM2连击技能设置和DB完整数据
连击技能设置: M2\选项\功能设置\技能魔法\通用技能\连击技能 魔法DB: 81;倚天辟地;0;55;5;10;10;5;6;6;99;15;5;15;10;15;15;60;; 300;万剑归宗 ...
- Move-to-front(MTF) and Run-lenght encoding(RLE) algorithms
mtf算法(关于该算法:https://www2.cs.duke.edu/csed/algoprobs/beta/bw1.html): #include <stdio.h> #includ ...
- centos 6.5 升级到 python2.7
准备 centos6.5的python版本默认是2.6.6,可能有的时候我们需要升级到更高的版本,那就来动手升级下吧.我这里以2.7.8版本为例,根据实际需要选择升级版本即可. yum install ...
- laravel 模拟数据批量添加
模拟User表结构: database/factories/UserFactory.php(模型工厂) <?php use App\Models\User; use Illuminate\Sup ...
- 《FA分享》---创业学习--训练营直播第二课--HHR
盛沛涵,以太白泽董事 一,基金投资的出发点: 1,这个赛道是否只有头部一两名有机会,如果不是,投的概率更大. 2, 基金投资的判断逻辑: 1.我是不是要在这个赛道布局 2.这个赛道分布如何,有 ...
- win10使用L2TP连接失败,报远程服务器未响应错误解决办法,亲测可用!
报错如下: 原因是L2TP连接需要IPSec加密,远程服务器未响应说明IPSec加密被禁用了,需要在注册表启用它,具体步骤如下: 1.以管理员账号身份打开CMD,win10 是 win+x键 2.找到 ...