How do I manage transactions with try-with-resources in EK9?
← Dependency Injection · Ref: Q333
The simplest and most recommended transaction pattern uses try-with-resources with a type implementing the Transaction trait. Transaction extends Closeable, so operator close provides automatic cleanup.
BASIC PATTERN
Create a transaction in the try header, use it in the body, commit on success:
try -> txn <- DatabaseTransaction(connection) updateRecords(txn) txn.commit() catch -> ex as Exception handleError(ex)
When the try scope exits, operator close is called automatically.
AUTO-ROLLBACK SAFETY NET
The operator close implementation can check isCommitted() and rollback if the transaction was not committed. This prevents partial commits when exceptions occur between operations.
WHY THIS PATTERN IS BEST
1. TRANSACTION SCOPE IS VISIBLE: the try block clearly delimits transaction boundaries
2. AUTOMATIC CLEANUP: operator close is called regardless of how the scope exits
3. EXCEPTION SAFE: exceptions trigger automatic cleanup, no finally block needed
4. AI FRIENDLY: AI can see the transaction scope and generate correct code
5. SIMILAR TO RUST: mirrors Diesel's RAII pattern and Go's defer
COMPARISON WITH SPRING
Spring @Transactional hides the transaction boundary. Self-invocation bypasses the proxy. Checked exceptions silently commit. EK9's try-with-resources has none of these failure modes.
See Q137 for try-with-resources basics. See Q134 for try/catch patterns. See Q334 for delegation pattern. See Q335 for callback pattern. See Q338 for partial commit prevention.
Example
defines module qa.di.transaction.try.resources defines trait <?- Connection abstraction for the example. -?> Connection execute() -> statement as String <- result as String? defines class <?- A concrete transaction that tracks commit state. Implements Transaction trait (which extends Closeable). -?> DatabaseTransaction with trait of Transaction connectionName <- String() committed <- false DatabaseTransaction() -> connectionName as String this.connectionName: connectionName executeUpdate() -> statement as String <- result as String: `${connectionName}: ${statement}` override commit() committed: true override rollback() committed: false override isCommitted() as pure <- rtn as Boolean: committed override operator close as pure stdout <- Stdout() stdout.println("Close: committed=" + $committed) override operator ? as pure <- rtn <- true defines program TransactionTryResourcesDemo() stdout <- Stdout() // === TRY-WITH-RESOURCES: most explicit pattern === stdout.println("=== Successful transaction ===") try -> txn <- DatabaseTransaction("primary-db") result1 <- txn.executeUpdate("UPDATE accounts SET balance = 100") stdout.println(result1) result2 <- txn.executeUpdate("INSERT INTO audit_log VALUES ('transfer')") stdout.println(result2) txn.commit() catch -> ex as Exception stdout.println("Error: " + $ex) // === UNCOMMITTED TRANSACTION: close called without commit === stdout.println("=== Uncommitted transaction ===") try -> txn <- DatabaseTransaction("secondary-db") result3 <- txn.executeUpdate("UPDATE inventory SET count = 0") stdout.println(result3) // No commit — operator close sees committed=false catch -> ex as Exception stdout.println("Error: " + $ex)
Common mistakes
E08180 — Class fields must be initialized inline at declaration. Declaring a field without initialization produces a field-not-initialized error unless the field is an injection point ('!'). See ek9 -h E08180 for details.
Incorrect:
connectionName as String
Correct:
connectionName <- String()
Other ways to ask this
- How do I use the Transaction trait with try-with-resources?
- What is the simplest transaction pattern in EK9?
- How does operator close work with transactions?
Coming from another language?
Java Spring: @Transactional annotation with proxy-based AOP, seven silent failure modes. Go: defer tx.Rollback() after tx, err := db.Begin(). Rust: conn.transaction(|txn| { ... }) RAII closure. C#: using (var scope = new TransactionScope()) { ... scope.Complete(); }. Python: with conn.begin() as txn: .... EK9: try -> txn <- Transaction() with operator close for auto-cleanup, isCommitted() check in close for safety.
Keywords: safety, rollback, migrate, try, transaction, dependency, cleanup, commit, inject, close, automatic, resources, scope