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#


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM