Transactions & Connections
Connection lifecycle, ConnectionManager, the transaction {} function, nested savepoints, deferred side effects, and how GraphQL requests manage connections.
ConnectionManager
ConnectionManager is the per-request handle to a database connection. It is lazy — no physical connection is obtained from the pool until the first SQL operation.
It lives in the coroutine context. Repository methods, transaction {}, and RequestCache all access it automatically.
The transaction {} Function
Wraps a block in a database transaction. If the block completes normally, it commits. If it throws, it rolls back and the exception propagates.
NonCancellable context — coroutine cancellation cannot leave a transaction half-open. Nested Transactions via Savepoints
Calling transaction {} inside another transaction {} creates a SQL SAVEPOINT:
- Inner commit releases the savepoint (does not commit to the database).
- Inner rollback rolls back to the savepoint (outer transaction continues).
- Only the outermost commit actually calls
connection.commit().
GraphQL Connection Management
GraphQL requests work differently from HTTP routes:
- Each field resolver gets its own
ConnectionManager— no shared transaction across fields. - Connections are acquired lazily per field — fields that don't touch the database never acquire a connection.
- A
ConnectionLimitersemaphore (default limit 5) prevents a single GraphQL request from holding too many concurrent connections.
Deferred Side Effects
Several systems register callbacks to defer side effects until after the transaction commits:
- Remote cache writes —
RequestCacheflushes pending puts/removes ononCommit(). Rolled-back writes are discarded. - Job queue enqueues —
dispatch()defers viadbRunAfterCommit. Background jobs don't start before data is visible. - Pub-sub publishing — subscribers don't receive notifications about uncommitted data.
Background Jobs
For code outside a request context, use withConnectionManager: