Skip to main content

10.3 Programmatic Transactions

Programmatic transactions use TransactionManager directly. This provides the most flexibility but is also easier to misuse; use it when precise control over transaction state is needed.

Suitable For

  • You need to retain TransactionStatus manually.
  • One method must explicitly begin and finish multiple transactions.
  • You need to inspect state through hasTransaction() or isTopTransaction(...).

Not Suitable For

The following snippets belong in methods that declare the required exceptions. JdbcTemplate and the transaction manager must use the same DataSource.

Basic Usage

Manual Commit and Rollback
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;
}

Behavior:

  • If both statements succeed, commit(tran) commits the transaction.
  • If SQL fails and the transaction is unfinished, attempt rollback. A failed commit does not mean rollback succeeded; inspect the exception and database state to determine the outcome.

Specify Propagation and Isolation

Begin an Independent Transaction
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 suspends any existing transaction on the current thread and creates a new connection for this transaction.

Roll Back Only a Nested Scope

NESTED creates a savepoint within an existing transaction. Inner rollback returns to that savepoint without directly ending the outer transaction.

Use NESTED to Roll Back Local Work
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;
}
// Continue only if business rules allow this coupon failure; record its cause in a real application.
}

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;
}

Behavior:

  • If the coupon write fails, only its order_coupon savepoint is rolled back.
  • The outer order and items may still commit.
  • If the outer transaction rolls back, successful nested work rolls back with it.

Obtain TransactionManager

Method 1: Obtain via TransactionHelper
TransactionManager txManager = TransactionHelper.txManager(dataSource);

TransactionHelper.txManager(dataSource) reuses the same manager instance for a DataSource.

Method 2: Create LocalTransactionManager
import net.hasor.dbvisitor.transaction.support.LocalTransactionManager;

TransactionManager txManager = new LocalTransactionManager(dataSource);
Method 3: Dependency Injection
public class OrderService {
// @Inject < Guice, Solon and Hasor
// @Resource or @Autowired < Spring
private TransactionManager txManager;
}

Usage Notes

  • Call commit(...) or rollBack(...) once on an unfinished transaction.
  • If the target is not at the top of the stack, dbVisitor first processes scopes opened after it.
  • TransactionManager implements Closeable, but explicit commit or rollBack calls make ordinary transaction boundaries clearer.

Further Reading

  • Cross-API Transactions: Let different APIs join a transaction opened programmatically.
  • Propagation: Understand each Propagation value's effect on connections, savepoints, and suspension.
  • Isolation: Understand how Isolation maps to JDBC connection isolation.