闲谭SpringBoot--ShardingSphere分布式事务探究
文章目录
- 0. 背景
- 1. 未分库分表时
- 2. 仅分表时
- 3. 分库分表时
- 3.1 不涉及分库表
- 3.2 涉及分库表,且分库表处于一个库
- 3.3 涉及分库表,且分库表处于多个库
- 3.4 涉及分库表,且运行中某库停机
- 4. 小结
0. 背景
接上篇文章《闲谭SpringBoot–ShardingSphere分库分表探究》,我已经完成分库分表的项目搭建。
本篇探究在不同情况下,事务是否正常运作。
1. 未分库分表时
针对不分库的user表,故意抛出异常,看看能否回滚。
执行前:
执行代码:
/**
* 在未分库分表的情况下,可以正常回滚
*/
@Transactional(rollbackFor = Exception.class)
public int testWithNoDivide() throws Exception {
UserDo user = new UserDo();
user.setId("2");
user.setName("testWithNoDivide");
int re = userService.add(user);
if (true) {
throw new Exception("err");
}
return re;
}
执行后,由于抛出异常,事务回滚,数据库中的内容并未改变。
测试通过。
2. 仅分表时
如果将log表按时间进行分表,我们看能否回滚。
首先验证只使用@Transactional(rollbackFor = Exception.class)
能否回滚多个表:
/**
* 在仅分表的情况下,测试是否可以回滚
*/
@Transactional(rollbackFor = Exception.class)
public int testWithTableDivide() throws Exception {
// 改用户
UserDo user = new UserDo();
user.setId("2");
user.setName("testWithTableDivide");
int reUser = userService.add(user);
// 改日志
LogDo log = new LogDo();
log.setId("111");
log.setTime("2025-01-01 11:11:11");
log.setContent("testWithTableDivide");
int reLog = logService.add(log);
// 抛出异常
if (true) {
throw new Exception("err");
}
return reUser + reLog;
}
通过验证,发现是可以正常回滚的,所以在仅分表不分库的情况下,使用@Transactional(rollbackFor = Exception.class)
足矣。
3. 分库分表时
如果既分库、又分表,是否需要开启分布式事务呢?
我们分两步验证,首先验证不涉及分库的表时,其次验证涉及分库的表时。
3.1 不涉及分库表
此时对log分库分表,测试user是否能正常回滚:
/**
* 在未分库分表的情况下,可以正常回滚
*/
@Transactional(rollbackFor = Exception.class)
public int testWithNoDivide() throws Exception {
UserDo user = new UserDo();
user.setId("2");
user.setName("testWithNoDivide");
int re = userService.add(user);
if (true) {
throw new Exception("err");
}
return re;
}
这个应该是没有悬念的,不涉及分库的表,没必要开启分布式事务。
3.2 涉及分库表,且分库表处于一个库
我们同时修改user表、log表,但是让user和log处于一个数据库ds0里面,测试能否回滚
/**
* 涉及分库表,且分库表处于一个库
*/
@Transactional(rollbackFor = Exception.class)
public int testDbDivdeButInOneDb() throws Exception {
// 改用户
UserDo user = new UserDo();
user.setId("2");
user.setName("testDbDivdeButInOneDb");
int reUser = userService.add(user);
// 改日志1
LogDo log1 = new LogDo();
log1.setId("111");
log1.setTime("2025-01-01 11:11:11");
log1.setContent("testDbDivdeButInOneDb");
log1.setDepartId("0");
int reLog1 = logService.add(log1);
// 改日志2
LogDo log2 = new LogDo();
log2.setId("222");
log2.setTime("2025-02-02 11:11:11");
log2.setContent("testDbDivdeButInOneDb");
log2.setDepartId("0");
int reLog2 = logService.add(log2);
// 抛出异常
if (true) {
throw new Exception("err");
}
return reUser + reLog1 + reLog2;
}
正常回滚,说明当表在一个库中时,使用@Transactional(rollbackFor = Exception.class)
即可。
3.3 涉及分库表,且分库表处于多个库
我们再构造一个在不同库的例子,看能否回滚。
/**
* 涉及分库表,且分库表处于多个库
*/
@Transactional(rollbackFor = Exception.class)
public int testDbDivdeButInTwoDb() throws Exception {
// 改日志1
LogDo log1 = new LogDo();
log1.setId("111");
log1.setTime("2025-01-01 11:11:11");
log1.setContent("testDbDivdeButInOneDb");
log1.setDepartId("0");//在库ds0中!
int reLog1 = logService.add(log1);
// 改日志2
LogDo log2 = new LogDo();
log2.setId("222");
log2.setTime("2025-02-02 11:11:11");
log2.setContent("testDbDivdeButInOneDb");
log2.setDepartId("1");//在库ds1中!
int reLog2 = logService.add(log2);
// 抛出异常
if (true) {
throw new Exception("err");
}
return reLog1 + reLog2;
}
实测证明依然回滚。
3.4 涉及分库表,且运行中某库停机
我们启动项目后,将ds1停止运行,然后测试,依然可以。
4. 小结
经查询文档,在不指定TransactionType,默认采用的TransactionType.LOCAL枚举值,这个用法已经够用了。
可以通过@ShardingTransactionType(TransactionType.XA)
改为其他事务类型。