← Back to Stormify Documentation

TransactionContext

Context for executing database operations within a transaction.

All operations performed through this context share the same database connection, ensuring they participate in the same transaction. The transaction is committed if all operations succeed, or rolled back if any operation throws an exception.

Basic Usage

stormify.transaction {
val user = create(User(email = "test@example.com"))
create(Profile(userId = user.id, name = "Test User"))
update(account)
}

Nested Transactions

Nested transactions are implemented using database savepoints:

stormify.transaction {
create(record1)

transaction { // Creates a savepoint
create(record2)
// If this fails, only record2 is rolled back
}

create(record3) // Still executes
}

Extracting Complex Logic

Pattern 1: Extension Functions (Recommended)

Define extension functions on TransactionContext for clean, reusable business logic:

fun TransactionContext.registerUser(email: String, name: String) {
val user = create(User(email = email))
create(Profile(userId = user.id, name = name))
create(AuditLog(action = "User registered", userId = user.id))
}

fun TransactionContext.transferFunds(from: Account, to: Account, amount: Double) {
require(from.balance >= amount) { "Insufficient funds" }
update(from.copy(balance = from.balance - amount))
update(to.copy(balance = to.balance + amount))
create(Transaction(fromId = from.id, toId = to.id, amount = amount))
}

// Usage
stormify.transaction {
registerUser("alice@example.com", "Alice")
transferFunds(accountA, accountB, 100.0)
}

Pattern 2: Service Layer Classes

Encapsulate transaction logic in service classes for structured applications:

class UserService(private val tx: TransactionContext) {
fun registerUser(email: String, name: String) {
val user = tx.create(User(email = email))
tx.create(Profile(userId = user.id, name = name))
}

fun deleteUser(userId: Int) {
val user = tx.findById<User>(userId)
tx.delete(user)
}
}

// Usage
stormify.transaction {
val service = UserService(this)
service.registerUser("alice@example.com", "Alice")
}

See also

Functions

Link copied to clipboard
fun <T : Any> create(item: T): T

Inserts a new entity into the database and returns it with generated values populated.

fun <T : Any> create(items: Collection<T>): List<T>

Inserts multiple entities in a batch.

Link copied to clipboard
fun <T : Any> delete(deletedItem: T)

Deletes an entity from the database based on its primary key.

fun <T : Any> delete(items: Collection<T>)

Deletes multiple entities from the database.

Link copied to clipboard
fun executeUpdate(query: String, vararg params: Any?): Int

Executes an SQL UPDATE/INSERT/DELETE and returns the number of affected rows.

Link copied to clipboard
inline fun <T : Any> findAll(whereClause: String = "", vararg arguments: Any?): List<T>

Finds all entities of type T, optionally filtered by a whereClause.

fun <T : Any> findAll(kclass: KClass<T>, whereClause: String = "", vararg arguments: Any?): List<T>

Finds all entities of kclass, optionally filtered by a whereClause.

Link copied to clipboard
inline fun <T : Any> findById(id: Any): T?

Finds a single entity of type T by its primary key id, or null if not found.

fun <T : Any> findById(kclass: KClass<T>, id: Any): T?

Finds a single entity of kclass by its primary key id, or null if not found.

Link copied to clipboard
inline fun <D : Any> getDetails(parent: Any, propertyName: String? = null): List<D>

Retrieves all detail (child) entities of type D related to a parent through a foreign key.

inline fun <D : Any> getDetails(parent: Any, referenceField: ReferencePath): List<D>

Type-safe variant of getDetails that accepts an annotation-processor-generated reference path (e.g. Paths.AuditEntry_.createdBy) instead of a string.

fun <M : Any, D : Any> getDetails(parent: M, detailsClass: KClass<D>, propertyName: String? = null): List<D>

Retrieves all detail (child) entities of detailsClass related to a parent through a foreign key.

Link copied to clipboard
fun <T : Any> populate(entity: T): T

Refreshes an entity with fresh data from the database based on its primary key.

Link copied to clipboard
fun procedure(name: String, vararg args: Any?)

Calls a stored procedure by name. OUT/INOUT parameters use Sp.Out/Sp.InOut.

Link copied to clipboard
inline fun <T : Any> read(query: String, vararg params: Any?): List<T>

Executes a SELECT query and returns all results as a list.

Link copied to clipboard
inline fun <T : Any> readCursor(query: String, vararg params: Any?, noinline consumer: (T) -> Unit): Int

Executes a SELECT query and processes results row-by-row via consumer. Returns the row count.

Link copied to clipboard
inline fun <T : Any> readOne(query: String, vararg params: Any?): T?

Executes a SELECT query and returns exactly one result, or null if none found.

Link copied to clipboard
fun transaction(block: () -> Unit)

Executes a nested transaction using a database savepoint.

Link copied to clipboard
fun <T : Any> update(updatedItem: T): T

Updates an existing entity in the database based on its primary key.

fun <T : Any> update(items: Collection<T>): List<T>

Updates multiple entities in a batch.