跳到主要内容

10.3 编程式事务

编程式事务直接使用 TransactionManager。它最灵活,也最容易写错,适合需要精确控制事务状态的场景。

适合​

  • 需要手动保留 TransactionStatus。
  • 一个方法里要显式开启多个事务,并控制它们的提交或回滚。
  • 想直接观察 hasTransaction()、isTopTransaction(...) 等事务状态。

不适合​

以下是可放入抛出异常的方法中的片段。JdbcTemplate 与事务管理器必须使用同一个 DataSource。

基本用法​

手动提交和回滚
import net.hasor.dbvisitor.transaction.TransactionManager;
import net.hasor.dbvisitor.transaction.TransactionStatus;
import net.hasor.dbvisitor.transaction.support.TransactionHelper;

TransactionManager txManager = TransactionHelper.txManager(dataSource);

TransactionStatus tran = txManager.begin();
try {
jdbcTemplate.executeUpdate(
"insert into orders(id, user_id) values(?, ?)",
new Object[] { orderId, userId }
);
jdbcTemplate.executeUpdate(
"insert into order_item(order_id, sku_id) values(?, ?)",
new Object[] { orderId, skuId }
);

txManager.commit(tran);
} catch (Throwable e) {
if (!tran.isCompleted()) {
try {
txManager.rollBack(tran);
} catch (Throwable rollbackError) {
e.addSuppressed(rollbackError);
}
}
throw e;
}

执行效果:

  • 两个 SQL 都成功,commit(tran) 提交事务。
  • SQL 失败且事务未完成时,尝试回滚;提交失败不等于已回滚,需根据异常和数据库状态判断结果。

指定传播行为和隔离级别​

开启独立事务
import net.hasor.dbvisitor.transaction.Isolation;
import net.hasor.dbvisitor.transaction.Propagation;

TransactionStatus auditTran = txManager.begin(
Propagation.REQUIRES_NEW,
Isolation.READ_COMMITTED
);
try {
jdbcTemplate.executeUpdate(
"insert into order_audit(order_id, action) values(?, ?)",
new Object[] { orderId, "CREATE" }
);
txManager.commit(auditTran);
} catch (Throwable e) {
if (!auditTran.isCompleted()) {
try {
txManager.rollBack(auditTran);
} catch (Throwable rollbackError) {
e.addSuppressed(rollbackError);
}
}
throw e;
}

REQUIRES_NEW 会在当前线程已经有事务时挂起旧事务,并创建一个新连接执行当前事务。

只回滚内层嵌套事务​

NESTED 会在已有事务中创建保存点。内层回滚只回滚到保存点,不会直接结束外层事务。

使用 NESTED 回滚一段局部操作
TransactionStatus outer = txManager.begin();
try {
jdbcTemplate.executeUpdate(
"insert into orders(id, user_id) values(?, ?)",
new Object[] { orderId, userId }
);

TransactionStatus nested = txManager.begin(Propagation.NESTED);
try {
jdbcTemplate.executeUpdate(
"insert into order_coupon(order_id, coupon_id) values(?, ?)",
new Object[] { orderId, couponId }
);
txManager.commit(nested);
} catch (SQLException couponError) {
if (nested.isCompleted()) {
throw couponError;
}
try {
txManager.rollBack(nested);
} catch (Throwable rollbackError) {
couponError.addSuppressed(rollbackError);
throw couponError;
}
// 只有业务明确允许忽略该优惠券失败时才继续;实际应用记录失败原因。
}

jdbcTemplate.executeUpdate(
"insert into order_item(order_id, sku_id) values(?, ?)",
new Object[] { orderId, skuId }
);
txManager.commit(outer);
} catch (Throwable e) {
if (!outer.isCompleted()) {
try {
txManager.rollBack(outer);
} catch (Throwable rollbackError) {
e.addSuppressed(rollbackError);
}
}
throw e;
}

执行效果:

  • 优惠券写入失败时,只回滚 order_coupon 对应的保存点。
  • 外层订单和明细仍然可以继续提交。
  • 如果外层最终回滚,内层已经成功的保存点也会随外层一起回滚。

获取 TransactionManager​

方式 1:通过 TransactionHelper 获取
TransactionManager txManager = TransactionHelper.txManager(dataSource);

TransactionHelper.txManager(dataSource) 会为同一个 DataSource 复用同一个事务管理器实例。

方式 2:直接创建 LocalTransactionManager
import net.hasor.dbvisitor.transaction.support.LocalTransactionManager;

TransactionManager txManager = new LocalTransactionManager(dataSource);
方式 3:依赖注入
public class OrderService {
// @Inject < Guice、Solon 和 Hasor
// @Resource or @Autowired < Spring
private TransactionManager txManager;
}

注意事项​

  • commit(...) 和 rollBack(...) 只能对未完成的事务调用一次。
  • 如果提交或回滚的事务不在栈顶,dbVisitor 会先处理它之后开启的事务。
  • TransactionManager 实现了 Closeable,但常规代码中更推荐显式 commit 或 rollBack,让事务边界一眼可见。

深入阅读​

  • 跨 API 事务:手动开启事务后,让不同 API 参与同一事务。
  • 传播行为:了解每个 Propagation 对连接、保存点、挂起的影响。
  • 隔离级别:了解 Isolation 如何映射到 JDBC 连接隔离级别。