我们在 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)的更多相关文章

  1. 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 ...

  2. io.lettuce.core.RedisCommandExecutionException: ERR unknown command 'GEOADD'

    io.lettuce.core.RedisCommandExecutionException: ERR unknown command 'GEOADD' at io.lettuce.core.Exce ...

  3. MySQL事务在MGR中的漫游记—路线图

    欢迎访问网易云社区,了解更多网易技术产品运营经验.   MGR即MySQL Group Replication,是MySQL官方推出的基于Paxos一致性协议的数据高可靠.服务高可用方案.MGR在20 ...

  4. 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 ...

  5. io.lettuce.core.protocol.ConnectionWatchdog - Reconnecting, last destination was ***

    一.问题 redis起来后一直有重连的日志,如下图: 二.分析 参考lettuce-core的github上Issues解答https://github.com/lettuce-io/lettuce- ...

  6. springboot连接redis错误 io.lettuce.core.RedisCommandTimeoutException:

    springboot连接redis报错 超时连接不上  可以从以下方面排查 1查看自己的配置文件信息,把超时时间不要设置0毫秒 设置5000毫秒 2redis服务长时间不连接就会休眠,也会连接不上 重 ...

  7. Mongodb 的事务在python中的操作

    代码实现如下: import pymongo mgClient = pymongo.MongoClient("ip", "port") session = mg ...

  8. 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 ...

  9. 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 ...

随机推荐

  1. 【Vue CLI】从安装到构建项目再到目录结构的说明

    目录 1. 构建我们的项目 2. 目录结构说明 2.1 build目录 2.2 config目录 2.3 src目录 2.4 static目录 "Vue CLI是一个基于Vue.js进行快速 ...

  2. Jquery插件validate使用一则

    jquery.validate是一个基于jquery的非常优秀的验证框架,可以通过它迅速验证一些常见的输入,并且可以自己扩充自己的验证方法. 主要功能有: 验证url,email,number,len ...

  3. 使用ltp4j碰到Can't find dependent libraries报错信息的问题解决

    项目中使用了哈工大的自然语言处理模块ltp4j,使用idea工具集成到项目中之后,在本机运行没有问题,一切正常.打成war包,部署到服务器上,使用的时候报错Can't find dependent l ...

  4. Vue - 让水平滚动条(scroll bar)固定在浏览器的底部

    效果 踩坑经历 TLDR; 在几个小时的google和stack overflow的苦苦搜索后,无果. 经过自我思考,想到了一种实现方法: 整个页面是一个盒子,要出现滚动条,必然里面的元素要溢出.也即 ...

  5. vscode git连接github

    上一篇文章中介绍了vscode中git的简单使用方法vscode git的简单使用 上次只讲到了本地库的创建,这次说明下怎么push到github上 首先需要有一个github的账号  github官 ...

  6. Vue基础笔记2

    目录 1. 如何获取Vue对象中的成员? 2. pre指定 3. for循环 4. todolist 5. 分隔符 6. computed 计算后的 7. vue的生命周期(讲解不全) 8. watc ...

  7. as(android studio)的初次使用

    链接:https://blog.csdn.net/qq_28808627/article/details/50058805

  8. Python日期

    1. datatime from datetime import datetime, date now = datetime.now() print(now) # 2020-01-20 01:24:0 ...

  9. Ubuntu 18 设置静态 IP

    在Ubuntu 18中使用 netplan 命令 首先 cd /etc/netplan/ 找到 *.yaml文件(每个人可能不一样),编辑它 root@waydeserver:/etc/netplan ...

  10. VS2019 还原Resharper菜单位置

    方法: 第一步: Tools -> Customize -> Extensions Menu tab, uncheck Resharper 工具->自定义->扩展菜单-> ...