事务示例


我们通过一个简单的示例来演示一下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两种模式的实现。

results matching ""

    No results matching ""