我们在 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. [C++]蛇形填数

    [从左下角开始,逆时针蛇形填数] #include <iostream> using namespace std; int main() { int n; cin>>n; in ...

  2. Django框架之图书管理系统(二)

    该篇文章介绍多对多的关系介绍 一.数据库设计 一个作者对应多个书籍 一个书籍对应多个作者 总结也就是多对多之间的关系 通过模型类创建多对多之间的关系表的时候,Django框架通过ORM创建三个表,分别 ...

  3. Plastic Bottle Manufacturer Profile: Plastic Bottle Forming Process

    Plastic bottle molding refers to the process of making a final plastic product from a polymer produc ...

  4. 如何让DOS命令在新窗口打开

    可以调用别外的批处理如 start a.batstart b.batstart c.bat 新建a.bat.B.BAT.C.CAT,在这几个批处理中输入你的命令. 以上我自己测试通过.

  5. eclipse配置class注释模板

    /** * Copyright (c) ${year}, All Rights Reserved. * */ ${filecomment} ${package_declaration} ${typec ...

  6. 一组关于{x}的积分

    \[\Large\displaystyle \int_{0}^{1}\left \{ \frac{1}{x} \right \}\mathrm{d}x~,~\int_{0}^{1}\left \{ \ ...

  7. 分布式事务 --- CAP 理论

    本文部分来自参考资料!!半原创 概述 介绍CAP理论,并简单地证明了三存二的定论. CAP 理论 1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标.分别为 : C ...

  8. 洛谷 P1063 能量项链(区间DP)

    嗯... 题目链接:https://www.luogu.com.cn/problem/P1063 这道题首先要读懂题目,然后往上套区间dp,要转换成链式. AC代码: #include<cstd ...

  9. SSL握手两大加密算法 : RAS算法 和 DH算法解析

    写下此博客记录心得体会,如有不足之处请指正   先是手稿笔记 :  正文:   在Https协议中,Client端和Server端需要三个参数才能生成SessionKey来加密信息. 三个参数分别是 ...

  10. laravel 创建授权策略

    用户只能编辑自己的资料 在完成对未登录用户的限制之后,接下来我们要限制的是已登录用户的操作,当 id 为 1 的用户去尝试更新 id 为 2 的用户信息时,我们应该返回一个 403 禁止访问的异常.在 ...