You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/comparison.md
+64-5Lines changed: 64 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -84,6 +84,7 @@ JPA (typically implemented by Hibernate) is the most widely used persistence fra
84
84
### When to Choose Storm
85
85
86
86
- You want predictable, explicit database behavior
87
+
- You want concise entity definitions with minimal boilerplate
87
88
- N+1 queries have been a recurring problem
88
89
- You prefer immutable data structures
89
90
- You value simplicity over complexity
@@ -114,7 +115,7 @@ Spring Data JPA wraps JPA with a repository abstraction that derives query imple
114
115
115
116
### When to Choose Storm
116
117
117
-
- You want stateless, immutable entities
118
+
- You want stateless, immutable entities with minimal boilerplate
118
119
- You prefer explicit query logic over naming conventions
119
120
- You want to avoid JPA's complexity
120
121
- You want a lightweight, minimal dependency footprint
@@ -145,7 +146,7 @@ MyBatis is a SQL mapper that gives you full control over every query. You write
145
146
146
147
### When to Choose Storm
147
148
148
-
- You want automatic entity mapping without XML
149
+
- You want automatic entity mapping without XML and minimal boilerplate
149
150
- You prefer type-safe queries over string SQL
150
151
- You want relationships handled automatically
151
152
- You value compile-time safety
@@ -204,7 +205,7 @@ JDBI is a lightweight SQL convenience library that sits just above JDBC. It hand
204
205
205
206
### When to Choose Storm
206
207
207
-
- You want automatic entity mapping
208
+
- You want automatic entity mapping with concise entity definitions
208
209
- You need relationship handling
209
210
- You prefer type-safe queries over raw SQL
210
211
@@ -257,10 +258,68 @@ Storm supports all seven standard propagation modes natively in its `transaction
257
258
258
259
This means Storm's programmatic API can express patterns like audit logging (`REQUIRES_NEW`), defensive boundary enforcement (`MANDATORY`, `NEVER`), and non-transactional operations (`NOT_SUPPORTED`) directly in code, while Exposed requires Spring integration for these use cases. See [Transactions](transactions.md) for details and examples of each propagation mode.
259
260
261
+
#### Transaction Callbacks
262
+
263
+
Both frameworks allow running logic after a transaction commits or rolls back, but the APIs differ significantly.
264
+
265
+
Storm provides `onCommit` and `onRollback` callbacks on the `Transaction` object. Callbacks accept suspend functions, execute in registration order, and are resilient to individual failures (remaining callbacks still run). When a callback is registered inside a joined scope (`REQUIRED`, `NESTED`), it is automatically deferred to the outermost physical transaction's commit or rollback, so it only fires when data is actually durable:
266
+
267
+
```kotlin
268
+
transaction {
269
+
orderRepository.insert(order)
270
+
onCommit { emailService.sendConfirmation(order) } // Fires after physical commit
271
+
}
272
+
```
273
+
274
+
Exposed uses a `StatementInterceptor` interface with lifecycle methods (`beforeCommit`, `afterCommit`, `beforeRollback`, `afterRollback`, among others) that is registered on the transaction via `registerInterceptor()`. Global interceptors can be registered via Java `ServiceLoader`. This approach is well suited for cross-cutting concerns that apply to many transactions:
| Suspend support | Yes (JDBC) | R2DBC only (`SuspendStatementInterceptor`) |
292
+
| Nested transaction behavior | Deferred to physical commit | Fires after savepoint release (data not yet durable) |
293
+
| Callback isolation | Yes (remaining callbacks still run on failure) | No (exception propagates, skipping remaining interceptors) |
294
+
| Global interceptors | No | Yes (via `ServiceLoader`) |
295
+
| Additional hooks | No |`beforeCommit`, `beforeRollback`, `beforeExecution`, `afterExecution`|
296
+
297
+
The most significant behavioral difference is with nested transactions. Exposed's `afterCommit` fires on the nested transaction's own "commit," which for savepoint-based nesting is just a savepoint release, not an actual database commit. If the outer transaction subsequently rolls back, the `afterCommit` callback will have already executed despite the data never becoming durable. Storm avoids this by deferring callbacks to the outermost physical transaction.
298
+
299
+
Storm's callback isolation behavior (remaining callbacks still execute when one fails) follows the same approach as Spring's `TransactionSynchronization`, where post-commit and post-completion callbacks are invoked independently. Since callbacks fire after the transaction outcome is final, there is nothing to undo; silently skipping remaining side effects because of one failure would be worse than running them all and surfacing the first exception.
300
+
301
+
Exposed's `StatementInterceptor` also provides hooks that Storm intentionally does not offer: `beforeCommit`, `beforeRollback`, and statement-level interceptors (`beforeExecution`, `afterExecution`). In Storm's stateless model, pre-commit logic belongs at the end of the `transaction { }` block itself, since there is no persistence context to flush or dirty state to reconcile before the commit. Statement-level observability is covered by Storm's [`@SqlLog`](sql-logging.md) annotation and `SqlCapture` test utility rather than a general interceptor mechanism.
302
+
303
+
#### Schema Migration
304
+
305
+
Exposed provides built-in schema management through its `SchemaUtils` utility. You can create tables, add missing columns, and generate migration statements programmatically:
306
+
307
+
```kotlin
308
+
transaction {
309
+
SchemaUtils.create(UsersTable, OrdersTable) // CREATE TABLE IF NOT EXISTS
310
+
SchemaUtils.createMissingTablesAndColumns(UsersTable) // ALTER TABLE ADD COLUMN ...
311
+
SchemaUtils.statementsRequiredToActualizeScheme() // Returns DDL statements without executing
312
+
}
313
+
```
314
+
315
+
This is convenient for prototyping and simple applications. For production use, JetBrains recommends pairing Exposed with a dedicated migration tool like Flyway or Liquibase, since `SchemaUtils` does not handle column removal, type changes, or data migration.
316
+
317
+
Storm does not include schema management or migration utilities. Schema management is expected to be handled externally using tools like Flyway, Liquibase, or plain SQL scripts. Storm's [schema validation](validation.md) feature can verify at startup that entity definitions match the database schema, catching mismatches early without modifying the schema itself.
318
+
260
319
### When to Choose Storm
261
320
262
321
- You need Kotlin and Java support
263
-
- You want immutable entities without base class inheritance
322
+
- You want concise, immutable entities without base class inheritance
264
323
- You prefer annotation-based entity definitions
265
324
- N+1 queries are a concern
266
325
- You want relationships loaded automatically
@@ -298,7 +357,7 @@ Ktorm is a lightweight Kotlin ORM that uses entity interfaces and DSL-based tabl
298
357
### When to Choose Storm
299
358
300
359
- You need Kotlin and Java support
301
-
- You want immutable data classes, not interfaces
360
+
- You want concise, immutable data classes instead of mutable interfaces
0 commit comments