0
# Transaction Management
1
2
Comprehensive transaction management system supporting nested transactions, rollback semantics, lifecycle callbacks, and both synchronous and asynchronous execution patterns.
3
4
## Capabilities
5
6
### Transacter Interface
7
8
Main interface for synchronous transaction management.
9
10
```kotlin { .api }
11
/**
12
* A transaction-aware SqlDriver wrapper which can begin a Transaction on the current connection
13
*/
14
interface Transacter : TransacterBase {
15
/**
16
* Starts a Transaction and runs bodyWithReturn in that transaction
17
* @param noEnclosing If true, throws IllegalStateException if there's already an active transaction
18
* @param bodyWithReturn Lambda executed within the transaction context
19
* @return The result returned by bodyWithReturn
20
* @throws IllegalStateException if noEnclosing is true and there is already an active Transaction
21
*/
22
fun <R> transactionWithResult(
23
noEnclosing: Boolean = false,
24
bodyWithReturn: TransactionWithReturn<R>.() -> R
25
): R
26
27
/**
28
* Starts a Transaction and runs body in that transaction
29
* @param noEnclosing If true, throws IllegalStateException if there's already an active transaction
30
* @param body Lambda executed within the transaction context
31
* @throws IllegalStateException if noEnclosing is true and there is already an active Transaction
32
*/
33
fun transaction(
34
noEnclosing: Boolean = false,
35
body: TransactionWithoutReturn.() -> Unit
36
)
37
}
38
```
39
40
### SuspendingTransacter Interface
41
42
Asynchronous version of Transacter supporting coroutine-based transaction management.
43
44
```kotlin { .api }
45
/**
46
* A transaction-aware SqlDriver wrapper which can begin a Transaction on the current connection
47
*/
48
interface SuspendingTransacter : TransacterBase {
49
/**
50
* Starts a Transaction and runs bodyWithReturn in that transaction
51
* @param noEnclosing If true, throws IllegalStateException if there's already an active transaction
52
* @param bodyWithReturn Suspending lambda executed within the transaction context
53
* @return The result returned by bodyWithReturn
54
*/
55
suspend fun <R> transactionWithResult(
56
noEnclosing: Boolean = false,
57
bodyWithReturn: suspend SuspendingTransactionWithReturn<R>.() -> R
58
): R
59
60
/**
61
* Starts a Transaction and runs body in that transaction
62
* @param noEnclosing If true, throws IllegalStateException if there's already an active transaction
63
* @param body Suspending lambda executed within the transaction context
64
*/
65
suspend fun transaction(
66
noEnclosing: Boolean = false,
67
body: suspend SuspendingTransactionWithoutReturn.() -> Unit
68
)
69
}
70
```
71
72
### Transaction Context Interfaces
73
74
Interfaces available within transaction lambda scopes providing rollback and callback capabilities.
75
76
```kotlin { .api }
77
/**
78
* Transaction context for operations that return values
79
*/
80
interface TransactionWithReturn<R> : TransactionCallbacks {
81
/**
82
* Rolls back this transaction and returns the specified value
83
* @param returnValue Value to return after rollback
84
*/
85
fun rollback(returnValue: R): Nothing
86
87
/**
88
* Begin an inner transaction
89
* @param body Lambda executed within the nested transaction context
90
* @return Result of the nested transaction
91
*/
92
fun <R> transaction(body: TransactionWithReturn<R>.() -> R): R
93
}
94
95
/**
96
* Transaction context for operations that don't return values
97
*/
98
interface TransactionWithoutReturn : TransactionCallbacks {
99
/**
100
* Rolls back this transaction
101
*/
102
fun rollback(): Nothing
103
104
/**
105
* Begin an inner transaction
106
* @param body Lambda executed within the nested transaction context
107
*/
108
fun transaction(body: TransactionWithoutReturn.() -> Unit)
109
}
110
111
/**
112
* Suspending transaction context for operations that return values
113
*/
114
interface SuspendingTransactionWithReturn<R> : TransactionCallbacks {
115
/**
116
* Rolls back this transaction and returns the specified value
117
* @param returnValue Value to return after rollback
118
*/
119
fun rollback(returnValue: R): Nothing
120
121
/**
122
* Begin an inner transaction
123
* @param body Suspending lambda executed within the nested transaction context
124
* @return Result of the nested transaction
125
*/
126
suspend fun <R> transaction(body: suspend SuspendingTransactionWithReturn<R>.() -> R): R
127
}
128
129
/**
130
* Suspending transaction context for operations that don't return values
131
*/
132
interface SuspendingTransactionWithoutReturn : TransactionCallbacks {
133
/**
134
* Rolls back this transaction
135
*/
136
fun rollback(): Nothing
137
138
/**
139
* Begin an inner transaction
140
* @param body Suspending lambda executed within the nested transaction context
141
*/
142
suspend fun transactionWithResult(body: suspend SuspendingTransactionWithoutReturn.() -> Unit)
143
}
144
```
145
146
### Transaction Callbacks
147
148
Interface for registering post-commit and post-rollback lifecycle hooks.
149
150
```kotlin { .api }
151
/**
152
* Interface for registering transaction lifecycle callbacks
153
*/
154
interface TransactionCallbacks {
155
/**
156
* Queues function to be run after this transaction successfully commits
157
* @param function Lambda to execute after successful commit
158
*/
159
fun afterCommit(function: () -> Unit)
160
161
/**
162
* Queues function to be run after this transaction rolls back
163
* @param function Lambda to execute after rollback
164
*/
165
fun afterRollback(function: () -> Unit)
166
}
167
```
168
169
### Transaction Implementation Classes
170
171
Base classes for implementing transaction management functionality.
172
173
```kotlin { .api }
174
/**
175
* Base implementation class for synchronous transaction management
176
*/
177
abstract class TransacterImpl(driver: SqlDriver) :
178
BaseTransacterImpl(driver),
179
Transacter
180
181
/**
182
* Base implementation class for asynchronous transaction management
183
*/
184
abstract class SuspendingTransacterImpl(driver: SqlDriver) :
185
BaseTransacterImpl(driver),
186
SuspendingTransacter
187
188
/**
189
* Common base functionality for both synchronous and asynchronous transacter implementations
190
*/
191
abstract class BaseTransacterImpl(protected val driver: SqlDriver) {
192
/**
193
* For internal use, notifies the listeners that their underlying result set has changed
194
* @param identifier Query identifier for listener notification
195
* @param tableProvider Lambda that provides table names to notify
196
*/
197
protected fun notifyQueries(identifier: Int, tableProvider: ((String) -> Unit) -> Unit)
198
199
/**
200
* For internal use, creates a string in the format (?, ?, ?) where there are count question marks
201
* @param count Number of parameters
202
* @return Formatted parameter string
203
*/
204
protected fun createArguments(count: Int): String
205
}
206
```
207
208
**Usage Examples:**
209
210
```kotlin
211
import app.cash.sqldelight.Transacter
212
import app.cash.sqldelight.TransacterImpl
213
import app.cash.sqldelight.db.SqlDriver
214
215
// Implement a database class with transaction support
216
class UserDatabase(driver: SqlDriver) : TransacterImpl(driver) {
217
218
// Simple transaction without return value
219
fun deleteInactiveUsers() {
220
transaction {
221
// These operations happen atomically
222
execute("DELETE FROM user_sessions WHERE user_id IN (SELECT id FROM users WHERE active = 0)")
223
execute("DELETE FROM users WHERE active = 0")
224
225
// Register cleanup after successful commit
226
afterCommit {
227
println("Inactive users deleted successfully")
228
clearCache()
229
}
230
231
// Register error handling after rollback
232
afterRollback {
233
println("Failed to delete inactive users")
234
}
235
}
236
}
237
238
// Transaction with return value
239
fun transferMoney(fromUserId: Long, toUserId: Long, amount: Double): Boolean {
240
return transactionWithResult {
241
val fromBalance = getBalance(fromUserId)
242
val toBalance = getBalance(toUserId)
243
244
// Check sufficient funds
245
if (fromBalance < amount) {
246
rollback(false) // Rollback and return false
247
}
248
249
// Update balances
250
updateBalance(fromUserId, fromBalance - amount)
251
updateBalance(toUserId, toBalance + amount)
252
253
// Log the transaction
254
insertTransactionLog(fromUserId, toUserId, amount)
255
256
afterCommit {
257
sendNotification(fromUserId, "Money transferred successfully")
258
sendNotification(toUserId, "Money received")
259
}
260
261
true // Return success
262
}
263
}
264
265
// Nested transactions
266
fun createUserWithProfile(name: String, email: String, profileData: Map<String, String>): Long {
267
return transactionWithResult {
268
// Create user in outer transaction
269
val userId = insertUser(name, email)
270
271
// Create profile in nested transaction
272
transaction {
273
insertProfile(userId, profileData)
274
275
// If profile creation fails, both user and profile creation will be rolled back
276
// due to nested transaction semantics
277
}
278
279
userId
280
}
281
}
282
283
// Prevent nested transactions
284
fun criticalOperation() {
285
transaction(noEnclosing = true) {
286
// This will throw IllegalStateException if called within another transaction
287
performCriticalDatabaseUpdate()
288
}
289
}
290
}
291
292
// Asynchronous transaction management
293
class AsyncUserDatabase(driver: SqlDriver) : SuspendingTransacterImpl(driver) {
294
295
suspend fun processUserDataAsync(userId: Long): ProcessingResult {
296
return transactionWithResult {
297
val userData = fetchUserData(userId)
298
val processedData = processData(userData) // Suspending function
299
300
updateUserData(userId, processedData)
301
302
ProcessingResult.Success(processedData)
303
}
304
}
305
306
suspend fun batchUpdateUsersAsync(updates: List<UserUpdate>) {
307
transaction {
308
for (update in updates) {
309
updateUser(update.userId, update.data)
310
delay(10) // Suspending operation
311
}
312
313
afterCommit {
314
notifyBatchUpdateComplete()
315
}
316
}
317
}
318
}
319
```
320
321
### Thread Safety and Lifecycle
322
323
- **Transactions** are confined to their creation thread and must not escape the transaction lambda scope
324
- **Nested transactions** inherit the parent transaction's thread context
325
- **Rollback propagation** from inner transactions affects the entire transaction tree
326
- **Callback execution** happens synchronously after transaction completion
327
- **Error handling** during callbacks can create composite exceptions that preserve both original and callback errors