Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add-isolation-levels #28

Merged
merged 6 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ At the end of the withAutoCommit block, the AutoCommit ConnectionSession will be
## withTransaction

By using a Transaction ConnectionSession, changes will NOT be immediately committed to the database. Which allows for
multiple features listed below. If any of these features are required, use withTransaction.
multiple features listed below. If any of these features are required, use withTransaction. Also use withTransaction if you need to specify isolation level.

* Commit - Commits any existing changes to the database and clears any Savepoints and Locks
* Rollback - Reverts the changes since the most recent commit, or the beginning of the ConnectionSession if no commits
Expand All @@ -603,6 +603,18 @@ At the end of the withTransaction block, if the block is exited normally the Tra
exception is thrown, the Transaction will be rolled back. After the final commit/rollback, the Transaction ConnectionSession
will be closed.

### withTransaction - How to Specify Isolation levels

By default, all transactions run with `TRANSACTION_READ_COMMITTED`isolation level. The following shows how to specify a higher one:

```kotlin
db.withTransaction(isolationLevel = Db.IsolationLevel.TRANSACTION_REPEATABLE_READ)

db.withTransaction(isolationLevel = Db.IsolationLevel.TRANSACTION_SERIALIZABLE)
```

When the transaction is over, isolation level is restored to the default, TRANSACTION_READ_COMMITTED.

## DataSource configuration & AutoCommit

A dataSource has a default setting for the autocommit flag which can be configured. But the individual connections can
Expand Down
26 changes: 23 additions & 3 deletions src/main/kotlin/com/target/liteforjdbc/Db.kt
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,34 @@ open class Db(
* or rolls back if it throws an exception. This is required to perform any DB interactions that need transaction
* support.
*/
fun <T> withTransaction(block: (Transaction) -> T): T {
fun <T> withTransaction(
isolationLevel: IsolationLevel,
block: (Transaction) -> T
): T {
val transaction = Transaction(connection = dataSource.connection)
transaction.use {
val currentIsolationLevel = transaction.connection.transactionIsolation
return transaction.use {
try {
transaction.connection.transactionIsolation = isolationLevel.intCode
val result = block(transaction)
transaction.commit()
return result
result
} catch (t: Throwable) {
transaction.rollback()
throw t
} finally {
transaction.connection.transactionIsolation = currentIsolationLevel
}
}
}

/**
* Uses a com.target.liteforjdbc.Transaction, and commits it once to the block is executed successfully,
* or rolls back if it throws an exception. This is required to perform any DB interactions that need transaction
* support.
*/
fun <T> withTransaction(block: (Transaction) -> T): T = withTransaction(IsolationLevel.TRANSACTION_READ_COMMITTED, block)
chad-moller-target marked this conversation as resolved.
Show resolved Hide resolved

/**
* Uses a com.target.liteforjdbc.AutoCommit and closes it once teh block is executed. This can be useful to use a
* single connection from the DataSource for a series of actions. Using other convenience query methods on this
Expand Down Expand Up @@ -169,5 +183,11 @@ open class Db(
*/
fun isDataSourceHealthy() = useConnection { !it.isClosed }

enum class IsolationLevel(val intCode: Int) {
TRANSACTION_READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED),
TRANSACTION_REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ),
TRANSACTION_SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE)
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,34 @@ class PostgresSqlIntegrationTest {

finalCount shouldBe originalCount
}

@Test
fun setsReadCommittedIsolationLevel() {
checkIsolationLevel(Db.IsolationLevel.TRANSACTION_READ_COMMITTED, "read committed")
}

@Test
fun setsRepeatableReadIsolationLevel() {
checkIsolationLevel(Db.IsolationLevel.TRANSACTION_REPEATABLE_READ, "repeatable read")
}

@Test
fun setsSerializatableIsolationLevel() {
checkIsolationLevel(Db.IsolationLevel.TRANSACTION_SERIALIZABLE, "serializable")
}

private fun checkIsolationLevel(isolationLevel: Db.IsolationLevel, expected: String) {
val level = db.withTransaction(isolationLevel) { transaction ->
checkNotNull(
transaction.executeQuery(
"SELECT current_setting('transaction_isolation')"
) { resultSet: ResultSet ->
resultSet.getString("current_setting")
}
)
}
level shouldBe expected
}

private fun tableCount(): Int {
return checkNotNull(
Expand Down
Loading