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,让事务边界一眼可见。