Today, I hit an exception when I tried to code my Redis logic to be transactional.
io.lettuce.core.RedisCommandExecutionException: ERR EXEC without MULTI
Below is my code implementation.
1
2
3
4
5
redisTemplate.opsForHash().put("customer001", "salary", "5000");
redisTemplate.watch("customer001");
redisTemplate.multi();
redisTemplate.opsForHash().put("customer001", "age", "20");
redisTemplate.exec();
From the code you will notice that I actually used multi()
but somehow the framework not able to detect. I read from few blog stated transactional support is disabled by default in Spring Redis (see the source code below) and setEnableTransactionSupport(true)
will solve the issue.
1
2
3
4
5
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
private boolean enableTransactionSupport = false;
....
However, I still get the same exception after I set setEnableTransactionSupport(true)
1
2
3
4
5
6
redisTemplate.opsForHash().put("customer001", "salary", "5000");
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.watch("customer001");
redisTemplate.multi();
redisTemplate.opsForHash().put("customer001", "age", "20");
redisTemplate.exec();
Solution
I managed to fix the issue by using SessionCallback
provided by Spring Data Redis. It is an interface for use when multiple operations need to be performed with the same connection.
1
2
3
4
5
6
7
8
9
10
redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public <K, V> List<Object> execute(RedisOperations<K, V> operations) throws DataAccessException {
redisTemplate.opsForHash().put("customer001", "salary", "5000");
redisTemplate.watch("customer001");
redisTemplate.multi();
redisTemplate.opsForHash().put("customer001", "age", "20");
return redisTemplate.exec();
}
});
Note: This solution only work for Redis standalone mode as cluster mode does not support command like watch
. Execute the logic using Lua script could be the solution for cluster mode.
Reference