From b1cd2916e4d0cafba2c0e28c3f81fc85b949485a Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 28 May 2021 18:20:10 +0530 Subject: [PATCH 1/4] Fix typo: Rename file to 'DataSourceExtensions' --- .../norm/{DataSoruceExtensions.kt => DataSourceExtensions.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename runtime/src/main/kotlin/norm/{DataSoruceExtensions.kt => DataSourceExtensions.kt} (100%) diff --git a/runtime/src/main/kotlin/norm/DataSoruceExtensions.kt b/runtime/src/main/kotlin/norm/DataSourceExtensions.kt similarity index 100% rename from runtime/src/main/kotlin/norm/DataSoruceExtensions.kt rename to runtime/src/main/kotlin/norm/DataSourceExtensions.kt From a30dc2696ed5c63be00a146eb6d0f5a13e8ac481 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 28 May 2021 18:20:30 +0530 Subject: [PATCH 2/4] Add result model for transaction --- .../norm/transaction/TransactionResult.kt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 runtime/src/main/kotlin/norm/transaction/TransactionResult.kt diff --git a/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt b/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt new file mode 100644 index 0000000..315aeae --- /dev/null +++ b/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt @@ -0,0 +1,31 @@ +package norm.transaction + +/** + * A result model which will be returned on execution on [transaction]. + */ +sealed class TransactionResult { + data class Success(val data: R) : TransactionResult() + data class Error(val error: Throwable) : TransactionResult() + + /** + * Returns `true` if transaction is successful. Else `false`. + */ + val isSuccessful: Boolean = this is Success + + /** + * Forcefully tries to return a successful result. + * If transaction is not successful, it throws [IllegalStateException]. + */ + fun get(): R = runCatching { (this as Success).data } + .getOrElse { throw IllegalStateException("Transaction is not successful") } + + /** + * @return a result if transaction is successful otherwise null. + */ + fun getOrNull(): R? = if (this is Success) data else null + + /** + * @return an error if transaction is failed otherwise null. + */ + fun errorOrNull(): Throwable? = if (this is Error) error else null +} From ebf66979d7a8d6d08f6bd46c5c1f3ccda40ded68 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 28 May 2021 18:20:58 +0530 Subject: [PATCH 3/4] Create DataSource utility for executing transactions --- .../norm/transaction/TransactionResult.kt | 2 +- .../norm/transaction/TransactionUtil.kt | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 runtime/src/main/kotlin/norm/transaction/TransactionUtil.kt diff --git a/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt b/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt index 315aeae..bcf21c0 100644 --- a/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt +++ b/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt @@ -1,7 +1,7 @@ package norm.transaction /** - * A result model which will be returned on execution on [transaction]. + * A result model which will be returned on execution on [executeTransaction]. */ sealed class TransactionResult { data class Success(val data: R) : TransactionResult() diff --git a/runtime/src/main/kotlin/norm/transaction/TransactionUtil.kt b/runtime/src/main/kotlin/norm/transaction/TransactionUtil.kt new file mode 100644 index 0000000..4a70fd6 --- /dev/null +++ b/runtime/src/main/kotlin/norm/transaction/TransactionUtil.kt @@ -0,0 +1,45 @@ +package norm.transaction + +import java.sql.Connection +import java.sql.SQLException +import javax.sql.DataSource + +/** + * Executes queries specified by [block] in the transaction. + * + * @param block Lambda block with connection on which transaction queries will be executed. + * @return The transaction result. + * + * Example: + * + * ``` + * val result = dataSource.executeTransaction { + * val user = FindUserByIdQuery().query(it, FindUserByIdParams(id)) + * ReportUserViewedMessageCommand().command(it, ReportUserViewedMessageParams(messageId, user.id)) + * GetMessageViewCountQuery().query(it, GetMessageViewCountParams(messageId)).first() + * } + * + * // Check whether transaction is successful + * val isSuccessful = result.isSuccessful + * + * // Retrieve transaction result + * val viewCount = result.get() // or `result.getOrNull()` to retrieve safely. + * + * // Retrieve exception (if it's failed) + * val error = result.errorOrNull() + * ``` + */ +fun DataSource.executeTransaction(block: (Connection) -> R): TransactionResult { + return connection.use { connection -> + connection.autoCommit = false + try { + block(connection) + .also { connection.commit() } + .let { result -> TransactionResult.Success(result) } + } catch (exception: SQLException) { + exception.printStackTrace() + val rollbackError = runCatching { connection.rollback() }.exceptionOrNull() + TransactionResult.Error(rollbackError ?: exception) + } + } +} From 5d39e66e549437729f3eff21b3ef4aac672ac8d4 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 28 May 2021 19:01:36 +0530 Subject: [PATCH 4/4] Update documentation for fun `executeTransaction()` and `TransactionResult#get()` --- .../src/main/kotlin/norm/transaction/TransactionResult.kt | 2 +- runtime/src/main/kotlin/norm/transaction/TransactionUtil.kt | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt b/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt index bcf21c0..d5bef54 100644 --- a/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt +++ b/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt @@ -13,7 +13,7 @@ sealed class TransactionResult { val isSuccessful: Boolean = this is Success /** - * Forcefully tries to return a successful result. + * Forcefully tries to provide a successful result data. * If transaction is not successful, it throws [IllegalStateException]. */ fun get(): R = runCatching { (this as Success).data } diff --git a/runtime/src/main/kotlin/norm/transaction/TransactionUtil.kt b/runtime/src/main/kotlin/norm/transaction/TransactionUtil.kt index 4a70fd6..6318768 100644 --- a/runtime/src/main/kotlin/norm/transaction/TransactionUtil.kt +++ b/runtime/src/main/kotlin/norm/transaction/TransactionUtil.kt @@ -15,8 +15,8 @@ import javax.sql.DataSource * ``` * val result = dataSource.executeTransaction { * val user = FindUserByIdQuery().query(it, FindUserByIdParams(id)) - * ReportUserViewedMessageCommand().command(it, ReportUserViewedMessageParams(messageId, user.id)) - * GetMessageViewCountQuery().query(it, GetMessageViewCountParams(messageId)).first() + * AddUserItemOneCommand().command(it, AddUserItemOneParams(item1, user.id)) + * AddUserItemTwoCommand().command(it, AddUserItemTwoParams(item2, user.id)) * } * * // Check whether transaction is successful @@ -38,6 +38,7 @@ fun DataSource.executeTransaction(block: (Connection) -> R): TransactionResu .let { result -> TransactionResult.Success(result) } } catch (exception: SQLException) { exception.printStackTrace() + val rollbackError = runCatching { connection.rollback() }.exceptionOrNull() TransactionResult.Error(rollbackError ?: exception) }