From 2ac153e6db0a85dd8c341daabda57fce7f7c9dae Mon Sep 17 00:00:00 2001 From: AlexKuznetsov Date: Fri, 29 Sep 2023 14:22:27 -0500 Subject: [PATCH 1/5] add-isolation-levels --- src/main/kotlin/com/target/liteforjdbc/Db.kt | 26 +++++++++++++++-- .../postgres/PostgresSqlIntegrationTest.kt | 28 +++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/target/liteforjdbc/Db.kt b/src/main/kotlin/com/target/liteforjdbc/Db.kt index c1b1596..a3448f5 100644 --- a/src/main/kotlin/com/target/liteforjdbc/Db.kt +++ b/src/main/kotlin/com/target/liteforjdbc/Db.kt @@ -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 withTransaction(block: (Transaction) -> T): T { + fun 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 withTransaction(block: (Transaction) -> T): T = withTransaction(IsolationLevel.TRANSACTION_READ_COMMITTED, block) + /** * 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 @@ -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) + } + } diff --git a/src/test/kotlin/com/target/liteforjdbc/postgres/PostgresSqlIntegrationTest.kt b/src/test/kotlin/com/target/liteforjdbc/postgres/PostgresSqlIntegrationTest.kt index 76c8d79..196e15f 100644 --- a/src/test/kotlin/com/target/liteforjdbc/postgres/PostgresSqlIntegrationTest.kt +++ b/src/test/kotlin/com/target/liteforjdbc/postgres/PostgresSqlIntegrationTest.kt @@ -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( From 2b1fc55c800644066caf9986a0c9ccf116980346 Mon Sep 17 00:00:00 2001 From: AlexKuznetsov Date: Sat, 30 Sep 2023 08:34:42 -0500 Subject: [PATCH 2/5] document-it --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 33e8c9d..0658b81 100644 --- a/README.md +++ b/README.md @@ -585,7 +585,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 @@ -599,6 +599,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. +### Isolation levels and withTransaction + +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 From 4b0dfe4da3868e780cae31c74dfb3defc8243d9b Mon Sep 17 00:00:00 2001 From: AlexKuznetsov Date: Mon, 2 Oct 2023 13:09:00 -0500 Subject: [PATCH 3/5] remove-overloading --- src/main/kotlin/com/target/liteforjdbc/Db.kt | 4 ++-- .../target/liteforjdbc/postgres/PostgresSqlIntegrationTest.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/target/liteforjdbc/Db.kt b/src/main/kotlin/com/target/liteforjdbc/Db.kt index a3448f5..a0f4a04 100644 --- a/src/main/kotlin/com/target/liteforjdbc/Db.kt +++ b/src/main/kotlin/com/target/liteforjdbc/Db.kt @@ -118,7 +118,7 @@ open class Db( * or rolls back if it throws an exception. This is required to perform any DB interactions that need transaction * support. */ - fun withTransaction( + fun withTransactionAndIsolation( isolationLevel: IsolationLevel, block: (Transaction) -> T ): T { @@ -144,7 +144,7 @@ open class Db( * or rolls back if it throws an exception. This is required to perform any DB interactions that need transaction * support. */ - fun withTransaction(block: (Transaction) -> T): T = withTransaction(IsolationLevel.TRANSACTION_READ_COMMITTED, block) + fun withTransaction(block: (Transaction) -> T): T = withTransactionAndIsolation(IsolationLevel.TRANSACTION_READ_COMMITTED, block) /** * Uses a com.target.liteforjdbc.AutoCommit and closes it once teh block is executed. This can be useful to use a diff --git a/src/test/kotlin/com/target/liteforjdbc/postgres/PostgresSqlIntegrationTest.kt b/src/test/kotlin/com/target/liteforjdbc/postgres/PostgresSqlIntegrationTest.kt index 196e15f..63d9381 100644 --- a/src/test/kotlin/com/target/liteforjdbc/postgres/PostgresSqlIntegrationTest.kt +++ b/src/test/kotlin/com/target/liteforjdbc/postgres/PostgresSqlIntegrationTest.kt @@ -458,7 +458,7 @@ class PostgresSqlIntegrationTest { } private fun checkIsolationLevel(isolationLevel: Db.IsolationLevel, expected: String) { - val level = db.withTransaction(isolationLevel) { transaction -> + val level = db.withTransactionAndIsolation(isolationLevel) { transaction -> checkNotNull( transaction.executeQuery( "SELECT current_setting('transaction_isolation')" From dcbb251708305fe87cdd29251d36f4aaa0d14726 Mon Sep 17 00:00:00 2001 From: AlexKuznetsov Date: Mon, 2 Oct 2023 13:29:18 -0500 Subject: [PATCH 4/5] fix-README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bc1ec84..374948e 100644 --- a/README.md +++ b/README.md @@ -603,14 +603,14 @@ 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. -### Isolation levels and withTransaction +### withTransactionAndIsolation - 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.withTransactionAndIsolation(isolationLevel = Db.IsolationLevel.TRANSACTION_REPEATABLE_READ) - db.withTransaction(isolationLevel = Db.IsolationLevel.TRANSACTION_SERIALIZABLE) + db.withTransactionAndIsolation(isolationLevel = Db.IsolationLevel.TRANSACTION_SERIALIZABLE) ``` When the transaction is over, isolation level is restored to the default, TRANSACTION_READ_COMMITTED. From 5bc47a45deb587fae6d66e914818328a5c89868e Mon Sep 17 00:00:00 2001 From: AlexKuznetsov Date: Thu, 5 Oct 2023 08:32:00 -0500 Subject: [PATCH 5/5] revert-to-overloading --- README.md | 6 +++--- src/main/kotlin/com/target/liteforjdbc/Db.kt | 4 ++-- .../liteforjdbc/postgres/PostgresSqlIntegrationTest.kt | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 374948e..a36e647 100644 --- a/README.md +++ b/README.md @@ -603,14 +603,14 @@ 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. -### withTransactionAndIsolation - How to Specify Isolation levels +### 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.withTransactionAndIsolation(isolationLevel = Db.IsolationLevel.TRANSACTION_REPEATABLE_READ) + db.withTransaction(isolationLevel = Db.IsolationLevel.TRANSACTION_REPEATABLE_READ) - db.withTransactionAndIsolation(isolationLevel = Db.IsolationLevel.TRANSACTION_SERIALIZABLE) + db.withTransaction(isolationLevel = Db.IsolationLevel.TRANSACTION_SERIALIZABLE) ``` When the transaction is over, isolation level is restored to the default, TRANSACTION_READ_COMMITTED. diff --git a/src/main/kotlin/com/target/liteforjdbc/Db.kt b/src/main/kotlin/com/target/liteforjdbc/Db.kt index a0f4a04..a3448f5 100644 --- a/src/main/kotlin/com/target/liteforjdbc/Db.kt +++ b/src/main/kotlin/com/target/liteforjdbc/Db.kt @@ -118,7 +118,7 @@ open class Db( * or rolls back if it throws an exception. This is required to perform any DB interactions that need transaction * support. */ - fun withTransactionAndIsolation( + fun withTransaction( isolationLevel: IsolationLevel, block: (Transaction) -> T ): T { @@ -144,7 +144,7 @@ open class Db( * or rolls back if it throws an exception. This is required to perform any DB interactions that need transaction * support. */ - fun withTransaction(block: (Transaction) -> T): T = withTransactionAndIsolation(IsolationLevel.TRANSACTION_READ_COMMITTED, block) + fun withTransaction(block: (Transaction) -> T): T = withTransaction(IsolationLevel.TRANSACTION_READ_COMMITTED, block) /** * Uses a com.target.liteforjdbc.AutoCommit and closes it once teh block is executed. This can be useful to use a diff --git a/src/test/kotlin/com/target/liteforjdbc/postgres/PostgresSqlIntegrationTest.kt b/src/test/kotlin/com/target/liteforjdbc/postgres/PostgresSqlIntegrationTest.kt index 63d9381..196e15f 100644 --- a/src/test/kotlin/com/target/liteforjdbc/postgres/PostgresSqlIntegrationTest.kt +++ b/src/test/kotlin/com/target/liteforjdbc/postgres/PostgresSqlIntegrationTest.kt @@ -458,7 +458,7 @@ class PostgresSqlIntegrationTest { } private fun checkIsolationLevel(isolationLevel: Db.IsolationLevel, expected: String) { - val level = db.withTransactionAndIsolation(isolationLevel) { transaction -> + val level = db.withTransaction(isolationLevel) { transaction -> checkNotNull( transaction.executeQuery( "SELECT current_setting('transaction_isolation')"