Skip to content

Commit

Permalink
translate transaction exception
Browse files Browse the repository at this point in the history
  • Loading branch information
endink committed Oct 1, 2024
1 parent f3bd910 commit bf3bf79
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 115 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.labijie.infra.orm.configuration

import com.labijie.infra.orm.ExposedTransactionListener
import com.labijie.infra.orm.interceptor.InfraStatementInterceptor
import org.jetbrains.exposed.spring.SpringTransactionManager
import org.jetbrains.exposed.sql.DatabaseConfig
import org.jetbrains.exposed.sql.SchemaUtils
Expand Down Expand Up @@ -53,15 +52,15 @@ class InfraExposedAutoConfiguration : ApplicationContextAware {
}

@Bean
@ConditionalOnMissingBean(SpringTransactionManager::class)
@ConditionalOnMissingBean(JdbcExposedTransactionManager::class)
fun exposedSpringTransactionManager(
environment: Environment,
properties: InfraExposedProperties,
databaseConfig: DatabaseConfig,
dataSource: DataSource
): SpringTransactionManager {
): JdbcExposedTransactionManager {

val txm = SpringTransactionManager(dataSource, databaseConfig, false)
val txm = JdbcExposedTransactionManager(dataSource, databaseConfig, false)
txm.addListener(ExposedTransactionListener(environment, properties))
return txm
}
Expand Down Expand Up @@ -140,7 +139,6 @@ class InfraExposedAutoConfiguration : ApplicationContextAware {

override fun setApplicationContext(applicationContext: ApplicationContext) {
this.applicationContext = applicationContext
InfraStatementInterceptor.springContext = applicationContext
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/**
* @author Anders Xiao
* @date 2024-10-01
*/
package com.labijie.infra.orm.configuration


import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.transactions.transactionManager
import org.springframework.dao.DataAccessException
import org.springframework.jdbc.support.SQLExceptionSubclassTranslator
import org.springframework.jdbc.support.SQLExceptionTranslator
import org.springframework.transaction.TransactionDefinition
import org.springframework.transaction.TransactionSystemException
import org.springframework.transaction.support.AbstractPlatformTransactionManager
import org.springframework.transaction.support.DefaultTransactionStatus
import org.springframework.transaction.support.SmartTransactionObject
import java.sql.SQLException
import javax.sql.DataSource

/**
* Transaction Manager implementation that builds on top of Spring's standard transaction workflow.
*
* @param dataSource The data source that produces `Connection` objects.
* @param databaseConfig The configuration that defines custom properties to be used with connections.
* If none is specified, the default configuration values will be used.
* @property showSql Whether transaction queries should be logged. Defaults to `false`.
*/
open class JdbcExposedTransactionManager(
dataSource: DataSource,
databaseConfig: DatabaseConfig = DatabaseConfig {},
private val showSql: Boolean = false,
) : AbstractPlatformTransactionManager() {

private var _database: Database

private var _transactionManager: TransactionManager

private var exceptionTranslator: SQLExceptionTranslator = SQLExceptionSubclassTranslator()

private val threadLocalTransactionManager: TransactionManager
get() = _transactionManager

init {
_database = Database.connect(
datasource = dataSource, databaseConfig = databaseConfig
).apply {
_transactionManager = this.transactionManager
}

isNestedTransactionAllowed = databaseConfig.useNestedTransactions
}

/**
* ExposedConnection implements savepoint by itself
* `useSavepointForNestedTransaction` is use `SavepointManager` for nested transaction
*
* So we don't need to use java savepoint for nested transaction
*/
override fun useSavepointForNestedTransaction() = false

fun setExceptionTranslator(exceptionTranslator: SQLExceptionTranslator) {
this.exceptionTranslator = exceptionTranslator
}

override fun doGetTransaction(): Any {
val outerManager = TransactionManager.manager
val outer = threadLocalTransactionManager.currentOrNull()

return ExposedTransactionObject(
manager = threadLocalTransactionManager,
outerManager = outerManager,
outerTransaction = outer,
)
}

override fun doSuspend(transaction: Any): Any {
val trxObject = transaction as ExposedTransactionObject
val currentManager = trxObject.manager

return SuspendedObject(
transaction = currentManager.currentOrNull() as Transaction,
manager = currentManager,
).apply {
currentManager.bindTransactionToThread(null)
TransactionManager.resetCurrent(null)
}
}

override fun doResume(transaction: Any?, suspendedResources: Any) {
val suspendedObject = suspendedResources as SuspendedObject

TransactionManager.resetCurrent(suspendedObject.manager)
threadLocalTransactionManager.bindTransactionToThread(suspendedObject.transaction)
}

private data class SuspendedObject(
val transaction: Transaction,
val manager: TransactionManager
)

override fun isExistingTransaction(transaction: Any): Boolean {
val trxObject = transaction as ExposedTransactionObject
return trxObject.getCurrentTransaction() != null
}

override fun doBegin(transaction: Any, definition: TransactionDefinition) {
val trxObject = transaction as ExposedTransactionObject

val currentTransactionManager = trxObject.manager
TransactionManager.resetCurrent(threadLocalTransactionManager)

currentTransactionManager.newTransaction(
isolation = definition.isolationLevel,
readOnly = definition.isReadOnly,
outerTransaction = currentTransactionManager.currentOrNull()
).apply {
if (definition.timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
queryTimeout = definition.timeout
}

if (showSql) {
addLogger(StdOutSqlLogger)
}
}
}

override fun doCommit(status: DefaultTransactionStatus) {
try {
val trxObject = status.transaction as ExposedTransactionObject
TransactionManager.resetCurrent(trxObject.manager)
trxObject.commit()
}catch (e: Throwable) {
throw this.translateException("Exposed Commit", e)
}
}

override fun doRollback(status: DefaultTransactionStatus) {
try {
val trxObject = status.transaction as ExposedTransactionObject
TransactionManager.resetCurrent(trxObject.manager)
trxObject.rollback()
}catch (e: Throwable) {
throw this.translateException("Exposed Rollback", e)
}
}

override fun doCleanupAfterCompletion(transaction: Any) {
val trxObject = transaction as ExposedTransactionObject

trxObject.cleanUpTransactionIfIsPossible {
closeStatementsAndConnections(it)
}

trxObject.setCurrentToOuter()
}

private fun closeStatementsAndConnections(transaction: Transaction) {
val currentStatement = transaction.currentStatement
@Suppress("TooGenericExceptionCaught")
try {
currentStatement?.let {
it.closeIfPossible()
transaction.currentStatement = null
}
transaction.closeExecutedStatements()
} catch (error: Exception) {
exposedLogger.warn("Statements close failed", error)
}

@Suppress("TooGenericExceptionCaught")
try {
transaction.close()
} catch (error: Exception) {
exposedLogger.warn("Transaction close failed: ${error.message}. Statement: $currentStatement", error)
}
}

override fun doSetRollbackOnly(status: DefaultTransactionStatus) {
val trxObject = status.transaction as ExposedTransactionObject
trxObject.setRollbackOnly()
}


protected fun translateException(task: String, ex: Throwable): RuntimeException {
if(ex is SQLException) {
val dae: DataAccessException? = exceptionTranslator.translate(task, null, ex)
if (dae != null) {
return dae
}
}
throw TransactionSystemException(ex.message.orEmpty(), ex)
}

private data class ExposedTransactionObject(
val manager: TransactionManager,
val outerManager: TransactionManager,
private val outerTransaction: Transaction?,
) : SmartTransactionObject {

private var isRollback: Boolean = false

fun cleanUpTransactionIfIsPossible(block: (transaction: Transaction) -> Unit) {
val currentTransaction = getCurrentTransaction()
if (currentTransaction != null) {
block(currentTransaction)
}
}

fun setCurrentToOuter() {
manager.bindTransactionToThread(outerTransaction)
TransactionManager.resetCurrent(outerManager)
}

@Suppress("TooGenericExceptionCaught")
fun commit() {
try {
manager.currentOrNull()?.commit()
} catch (error: Exception) {
throw TransactionSystemException(error.message.orEmpty(), error)
}
}

@Suppress("TooGenericExceptionCaught")
fun rollback() {
try {
manager.currentOrNull()?.rollback()
} catch (error: Exception) {
throw TransactionSystemException(error.message.orEmpty(), error)
}
}

fun getCurrentTransaction(): Transaction? = manager.currentOrNull()

fun setRollbackOnly() {
isRollback = true
}


override fun isRollbackOnly() = isRollback

override fun flush() {
// Do noting
}
}
}

This file was deleted.

This file was deleted.

0 comments on commit bf3bf79

Please sign in to comment.