事务示例
我们通过一个简单的示例来演示一下UBSI事务注解的使用。先来看一下最基础的使用JDBC事务进行数据库操作的代码样例:
Connection conn = getConnection(); // 获得JDBC数据库连接
try {
conn.setAutoCommit(false); // 关闭自动提交,开启事务
// 执行insert指令
// 执行update指令
conn.commit(); // 事务提交
} catch(Exception e) {
conn.rollback(); // 失败,事务回滚
} finally {
conn.close(); // 关闭数据库连接
}
当这段代码需要放进一个微服务的业务接口,并参与到全局事务中时,可以改写代码如下:
private Connection conn = null; // 将JDBC连接作为服务对象的成员变量,配合SUBMIT_ORIGIN使用
@USTxTry(
propagation = Transaction.PROPAGATION_META, // 只操作本地事务,无后续分支事务
submit = Transaction.SUBMIT_ORIGIN // ”原地"提交
)
@USEntry(
tips = "事务接口demo",
params = {
... // 接口参数
},
timeout = 10 // 事务超时时间
)
public void demo(ServiceContext ctx, ...) throws Exception {
conn = getConnection(); // 获得JDBC数据库连接,并保持在服务对象实例中
conn.setAutoCommit(false); // 关闭自动提交,开启事务
// 执行insert指令
// 执行update指令
}
@USTxConfirm(entry="demo")
public void commitDemo(ServiceContext ctx) { // demo接口的commit
if ( conn != null ) {
conn.commit();
conn.close();
conn = null;
}
}
@USTxCancel(entry="demo")
public void rollbackDemo(ServiceContext ctx) { // demo接口的rollback
if ( conn != null ) {
conn.rollback();
conn.close();
conn = null;
}
}
使用SUBMIT_ORIGIN("原地"提交)的方式(传统XA模式),可以使得事务的操作基本与传统的本地事务保持一致,UBSI的服务容器会保持Try阶段的服务实例,用来响应后续的"commit/rollback"操作;如果直到timeout还未收到submit请求,容器会自动执行服务实例的@USTxCancel方法来"回滚"本地事务。
这种方式的弊端在于如果处于一个"长"事务中,分支事务会一直占用数据库的连接资源,不利于大规模并发。
在目前大多数的分布式事务方案中,采用的是基于"数据补偿"的实现机制,UBSI同样支持这种机制,只需要将上面的代码改写如下:
@USTxTry(
propagation = Transaction.PROPAGATION_META, // 只操作本地事务,无后续分支事务
submit = Transaction.SUBMIT_ANY // ”任意"提交
)
@USEntry(
tips = "事务接口demo",
params = {
... // 接口参数
},
timeout = 10 // 事务超时时间
)
public void demo(ServiceContext ctx, ...) throws Exception {
conn = getConnection(); // 获得JDBC数据库连接,并保持在服务对象实例中
try {
conn.setAutoCommit(false); // 关闭自动提交,开启事务
// 执行insert指令,记录insert回滚日志
// 执行update指令,记录update回滚日志
conn.commit(); // 直接提交本地事务
} catch(Exception e) {
conn.rollback(); // 失败,回滚
throw e;
} finally {
conn.close();
}
}
@USTxConfirm(entry="demo")
public void commitDemo(ServiceContext ctx) { // demo接口的commit
// 通过ServiceContext获得demo接口的Try阶段时的请求参数
// 删除相应的回滚日志(数据最终生效)
}
@USTxCancel(entry="demo")
public void rollbackDemo(ServiceContext ctx) { // demo接口的rollback
// 通过ServiceContext获得demo接口的Try阶段时的请求参数
// 根据相应的回滚日志恢复之前的数据
}
使用SUBMIT_ANY("任意"提交)可以更好的适应"数据补偿"方式(TCC模式)的实现:服务是无状态的,事务实例不需要被容器缓存;所以"数据补偿"方式能获得更好的容错能力、更高的灵活性,且不会造成数据库资源的长时间占用,但也有一些弊端:
- 代码实现更复杂
- 更多的数据操作
- 事务隔离级别只相当于"读未提交",会造成其他事务的"脏读"
所以具体在不同业务场景中使用何种分布式事务的具体实现,应该根据业务复杂度、系统对性能/可靠性的要求进行综合评估,不管采用什么机制,UBSI事务框架都能够提供很好的支持。
一个完整的"商品购买订单"处理的分布式事务的服务示例请见:https://github.com/ubsi-home/demo.transaction ,这个示例包含了XA/TCC两种模式的实现。