在Redis中使用事务,通常的命令组合是watch…multi…exec,也就是要在一个Redis连接中执行多个命令,我们可以考虑使用SessionCallback来达成这个目的。
watch: 监控Redis中的一些键
multi: 开始事务,但是在开始事务后,不会被立马执行,而是被存放在一个队列里,也就是说,我们执行一些返回数据的命令,Redis也不会马上执行,返回的数据也是null
exec: 执行事务,在执行队列里的命令前回去检查被监控的键是否发生过变化,如果发生了变化,则取消事务,如果没发生,就会执行事务,即要么全部执行,要么全部不执行,而且不会被其他客户端打断,保证数据的一致性。
如下代码清单:
redisTemplate.opsForValue().set("key1", "4");
List execute = redisTemplate.execute(new SessionCallback<List>() {
@Override
public List execute(RedisOperations operations) throws DataAccessException {
operations.watch("key1");
operations.multi();
operations.opsForValue().set("key2", "value2");
// operations.opsForValue().increment("key1", 1); // ②
Object value2 = operations.opsForValue().get("key2");
System.out.println("value2: " + value2);
return operations.exec(); // ①
}
});
System.out.println(execute);
- 我们第一次执行这段代码,发现第一个输出语句的value2为空。
- 我们清空Redis中的
key1
和key2
,第二次执行的时候在①
处打上断点,在代码执行到断点处的时候,通过Redis客户端修改键值为key1
的value值,然后继续执行代码,我们发现在Redis客户端中只有key1
的value为"4"
,也找不到key2
,这就是因为在执行operations.exec()
的时候,发现了被监控的key1
值在执行事务之前被修改了,所以就取消了事务的执行。 - 我们再次清空Redis中的
key1
和key2
,然后放开②
处的代码,再执行代码,我们知道key1
是个String
类型,在进行increment
操作的时候应该是会报错的,事实上在执行operations.exec()
的时候也确实报错了,不过我们查看Redis的时候发现,key2
的值也是同时存在的。这也是Redis事务的特点,所以我们在执行Redis事务前,需要严格地检查数据,才能避免这种情况的发生。