Declarative (#[Transactional])
Best for service-layer methods with straightforward, single-method transaction boundaries. Zero boilerplate — commit and rollback happen automatically based on whether the method throws.
Winter Boot supports two complementary approaches to transaction management. Declarative transactions use the #[Transactional] method annotation — the framework wraps the method in a transaction automatically, handling commit and rollback with no extra code in your business logic. Programmatic transactions give you fine-grained control via PlatformTransactionManager, which is useful when transaction boundaries cannot be expressed as a single method call or when you need to interact with dynamically-resolved data sources such as in multi-tenant scenarios.
Before using either approach, add #[EnableTransactionManagement] to your application entry-point class. Without this annotation, the transaction infrastructure is not bootstrapped.
use dev\winterframework\stereotype\WinterBootApplication;use dev\winterframework\core\app\WinterWebSwooleApplication;use dev\winterframework\txn\stereotype\EnableTransactionManagement;
#[EnableTransactionManagement]#[WinterBootApplication( configDirectory: [__DIR__ . '/config'], scanNamespaces: [ ['com\\example\\myapp', __DIR__ . '/src'] ])]class MyApplication { public static function main(): void { (new WinterWebSwooleApplication())->run(MyApplication::class); }}Annotate any service or component method with #[Transactional] to have the framework begin a transaction before the method runs, commit it on success, or roll it back on any uncaught exception.
use dev\winterframework\stereotype\Service;use dev\winterframework\txn\stereotype\Transactional;
#[Service]class PaymentService {
#[Transactional] public function processPayment(int $orderId, float $amount): void { // All database operations here run inside a single transaction. // Any uncaught exception triggers an automatic rollback. }}use dev\winterframework\stereotype\Service;use dev\winterframework\txn\stereotype\Transactional;
#[Service]class AdminService {
// Targets the "admindb" datasource's transaction manager #[Transactional("admindb-txn")] public function auditAction(string $action): void { // Runs inside a transaction on admindb }}use dev\winterframework\stereotype\Service;use dev\winterframework\txn\stereotype\Transactional;
#[Service]class WalletService {
#[Transactional(transactionManager: "myTxnMgr")] public function withdrawMoney(float $amount): void { // Runs under the custom transaction manager bean "myTxnMgr" }}transactionManager string (default: default)
Bean name of the PlatformTransactionManager to use. Defaults to the primary datasource’s transaction manager.
propagation int (default: PROPAGATION_REQUIRED)
Transaction propagation behaviour. See the propagation constants table below.
isolation int (default: ISOLATION_DEFAULT)
Database isolation level to request from the driver.
timeout int (default: TIMEOUT_DEFAULT)
Transaction timeout in seconds. -1 means use the driver default.
readOnly bool (default: false)
Mark the transaction as read-only. A rollback is performed at the end instead of a commit.
rollbackFor array (default: all exceptions)
Array of exception class names that must trigger a rollback.
noRollbackFor array (default: none)
Array of exception class names that must not trigger a rollback.
label array (default: [])
Arbitrary string labels to associate with the transaction for informational purposes.
| Constant | Value | Behaviour |
|---|---|---|
PROPAGATION_REQUIRED |
0 |
Use the current transaction, or create a new one if none exists (default) |
PROPAGATION_SUPPORTS |
1 |
Use the current transaction if one exists; otherwise run non-transactionally |
PROPAGATION_MANDATORY |
2 |
Use the current transaction; throw an exception if none exists |
PROPAGATION_REQUIRES_NEW |
3 |
Always create a new transaction, suspending any existing one |
PROPAGATION_NOT_SUPPORTED |
4 |
Always run non-transactionally, suspending any existing transaction |
PROPAGATION_NEVER |
5 |
Run non-transactionally; throw an exception if a transaction exists |
PROPAGATION_NESTED |
6 |
Run within a nested transaction if one exists; falls back to PROPAGATION_REQUIRED — not supported by PDO drivers |
PlatformTransactionManager gives you direct control over the transaction lifecycle. Inject it with #[Autowired] for the primary datasource, or by bean name for a named datasource, then call getTransaction(), commit(), and rollback() yourself.
getTransaction(TransactionDefinition $definition) TransactionStatus
Begin or join a transaction according to the given definition.
commit(TransactionStatus $status) void
Commit the transaction represented by $status.
rollback(TransactionStatus $status) void
Roll back the transaction represented by $status.
DefaultTransactionDefinition is the standard implementation of TransactionDefinition. Configure it before calling getTransaction():
use dev\winterframework\txn\support\DefaultTransactionDefinition;use dev\winterframework\txn\Transaction;
$def = new DefaultTransactionDefinition();$def->setPropagationBehavior(Transaction::PROPAGATION_REQUIRES_NEW);$def->setIsolationLevel(Transaction::ISOLATION_READ_COMMITTED);$def->setReadOnly(false);$def->setTimeout(30);The following service shows the full try/commit/catch/rollback pattern using an injected PlatformTransactionManager:
use dev\winterframework\pdbc\ex\EmptyResultDataAccessException;use dev\winterframework\pdbc\PdbcTemplate;use dev\winterframework\stereotype\Autowired;use dev\winterframework\stereotype\Service;use dev\winterframework\txn\PlatformTransactionManager;use dev\winterframework\txn\support\DefaultTransactionDefinition;use dev\winterframework\util\log\Wlf4p;
#[Service]class UserService { use Wlf4p;
#[Autowired] private PdbcTemplate $pdbc;
#[Autowired] private PlatformTransactionManager $txnMgr;
public function createUser(User $user): User { $status = $this->txnMgr->getTransaction(new DefaultTransactionDefinition()); try { $sql = "INSERT INTO users (name, email, age) VALUES (:name, :email, :age) RETURNING id"; $ret = []; $result = $this->pdbc->update($sql, [ 'name' => $user->getName(), 'email' => $user->getEmail(), 'age' => $user->getAge(), ], [], $ret);
if ($result) { $user->setId(intval($ret['id'])); }
$this->txnMgr->commit($status); return $user; } catch (\Throwable $e) { $this->txnMgr->rollback($status); throw $e; } }
public function findById(int $id): ?User { try { return $this->pdbc->queryForObject( "SELECT * FROM users WHERE id = :id", ['id' => $id], User::class ); } catch (EmptyResultDataAccessException) { return null; } }}PDO does not natively support nested (savepoint-based) transactions. PROPAGATION_NESTED is defined in the Transaction interface but falls back to PROPAGATION_REQUIRED behaviour when used with PDO-backed data sources.
Declarative (#[Transactional])
Best for service-layer methods with straightforward, single-method transaction boundaries. Zero boilerplate — commit and rollback happen automatically based on whether the method throws.
Programmatic (PlatformTransactionManager)
Best when transaction scope spans multiple methods, when you need conditional commits, or when working with multi-tenant dynamic data sources where the transaction manager is resolved at runtime.