diff --git a/exposed-spring-boot-starter/build.gradle.kts b/exposed-spring-boot-starter/build.gradle.kts index 3c8c4d332d..556a7805d9 100644 --- a/exposed-spring-boot-starter/build.gradle.kts +++ b/exposed-spring-boot-starter/build.gradle.kts @@ -31,4 +31,5 @@ tasks.withType(Test::class.java) { showStandardStreams = true exceptionFormat = TestExceptionFormat.FULL } + useJUnitPlatform() } diff --git a/exposed-spring-boot-starter/src/test/kotlin/org/jetbrains/exposed/jdbc-template/AuthorTable.kt b/exposed-spring-boot-starter/src/test/kotlin/org/jetbrains/exposed/jdbc-template/AuthorTable.kt new file mode 100644 index 0000000000..a7b2f98d7a --- /dev/null +++ b/exposed-spring-boot-starter/src/test/kotlin/org/jetbrains/exposed/jdbc-template/AuthorTable.kt @@ -0,0 +1,20 @@ +package org.jetbrains.exposed.`jdbc-template` + +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.UUIDEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.UUIDTable +import java.util.UUID + +object AuthorTable : UUIDTable("authors") { + val description = text("description") +} + +object BookTable : UUIDTable("books") { + val description = text("description") +} + +class Book(id: EntityID) : UUIDEntity(id) { + companion object : UUIDEntityClass(AuthorTable) + var description by AuthorTable.description +} diff --git a/exposed-spring-boot-starter/src/test/kotlin/org/jetbrains/exposed/jdbc-template/BookService.kt b/exposed-spring-boot-starter/src/test/kotlin/org/jetbrains/exposed/jdbc-template/BookService.kt new file mode 100644 index 0000000000..a675133620 --- /dev/null +++ b/exposed-spring-boot-starter/src/test/kotlin/org/jetbrains/exposed/jdbc-template/BookService.kt @@ -0,0 +1,54 @@ +package org.jetbrains.exposed.`jdbc-template` + +import org.jetbrains.exposed.sql.transactions.transaction +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.jdbc.core.JdbcTemplate +import org.springframework.stereotype.Component +import org.springframework.transaction.support.TransactionOperations +import java.util.UUID + +@Component +open class BookService( + @Qualifier("operations1") + private val operations1: TransactionOperations, + @Qualifier("operations2") + private val operations2: TransactionOperations, + private val jdbcTemplate: JdbcTemplate +) { + + fun testWithSpringAndExposedTransactions() { + transaction { + Book.new { description = "123" } + } + operations1.execute { + val id = UUID.randomUUID().toString() + val query = "insert into authors(id, description) values ('$id', '234234')" + jdbcTemplate.execute(query) + } + } + + fun testWithSpringTransaction() { + operations1.execute { + val id = UUID.randomUUID().toString() + val query = "insert into authors(id, description) values ('$id', '234234')" + jdbcTemplate.execute(query) + } + } + + fun testWithExposedTransaction() { + transaction { + Book.new { description = "1234" } + } + } + + fun testWithoutSpringTransaction() { + transaction { + Book.new { description = "1234" } + } + operations2.execute { + val id = UUID.randomUUID().toString() + val query = "insert into authors(id, description) values ('$id', '234234')" + jdbcTemplate.execute(query) + } + } +} diff --git a/exposed-spring-boot-starter/src/test/kotlin/org/jetbrains/exposed/jdbc-template/JdbcConfiguration.kt b/exposed-spring-boot-starter/src/test/kotlin/org/jetbrains/exposed/jdbc-template/JdbcConfiguration.kt new file mode 100644 index 0000000000..6757f85342 --- /dev/null +++ b/exposed-spring-boot-starter/src/test/kotlin/org/jetbrains/exposed/jdbc-template/JdbcConfiguration.kt @@ -0,0 +1,21 @@ +package org.jetbrains.exposed.`jdbc-template` + +import org.jetbrains.exposed.spring.SpringTransactionManager +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.transaction.support.TransactionOperations +import org.springframework.transaction.support.TransactionTemplate + +@Configuration +open class JdbcConfiguration { + @Bean + @Qualifier("operations1") + open fun operations1(transactionManager: SpringTransactionManager): TransactionOperations { + return TransactionTemplate(transactionManager) + } + + @Bean + @Qualifier("operations2") + open fun operations2() = TransactionOperations.withoutTransaction() +} diff --git a/exposed-spring-boot-starter/src/test/kotlin/org/jetbrains/exposed/jdbc-template/JdbcTemplateTests.kt b/exposed-spring-boot-starter/src/test/kotlin/org/jetbrains/exposed/jdbc-template/JdbcTemplateTests.kt new file mode 100644 index 0000000000..9c2391179c --- /dev/null +++ b/exposed-spring-boot-starter/src/test/kotlin/org/jetbrains/exposed/jdbc-template/JdbcTemplateTests.kt @@ -0,0 +1,54 @@ +package org.jetbrains.exposed.`jdbc-template` + +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction +import org.junit.jupiter.api.* +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.event.annotation.BeforeTestClass + +@SpringBootApplication +open class JdbcTemplateApplication + +@SpringBootTest( + classes = [JdbcTemplateApplication::class], + properties = ["spring.datasource.url=jdbc:h2:mem:test", "spring.datasource.driver-class-name=org.h2.Driver", "spring.exposed.generate-ddl=true"] +) +@TestMethodOrder(MethodOrderer.OrderAnnotation::class) +class JdbcTemplateTests { + + @BeforeTestClass + fun beforeTests() { + transaction { + SchemaUtils.create(AuthorTable, BookTable) + } + } + + @Autowired + lateinit var bookService: BookService + + @Order(1) + @RepeatedTest(15, name = "Without spring transaction: {currentRepetition}/{totalRepetitions}") + fun testWithoutSpringTransaction() { + bookService.testWithoutSpringTransaction() + } + + @Order(2) + @RepeatedTest(15, name = "With spring transaction: {currentRepetition}/{totalRepetitions}") + fun testWithSpringTransaction() { + bookService.testWithSpringTransaction() + } + + @Order(3) + @RepeatedTest(15, name = "With exposed transaction: {currentRepetition}/{totalRepetitions}") + fun testWithExposedTransaction() { + bookService.testWithExposedTransaction() + } + + @Order(4) + @RepeatedTest(15, name = "With spring and exposed transactions: {currentRepetition}/{totalRepetitions}") + fun testWithSpringAndExposedTransactions() { + bookService.testWithSpringAndExposedTransactions() + } +} diff --git a/spring-transaction/src/main/kotlin/org/jetbrains/exposed/spring/SpringTransactionManager.kt b/spring-transaction/src/main/kotlin/org/jetbrains/exposed/spring/SpringTransactionManager.kt index abed532499..37bf6870e1 100644 --- a/spring-transaction/src/main/kotlin/org/jetbrains/exposed/spring/SpringTransactionManager.kt +++ b/spring-transaction/src/main/kotlin/org/jetbrains/exposed/spring/SpringTransactionManager.kt @@ -17,7 +17,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager import javax.sql.DataSource class SpringTransactionManager( - private val _dataSource: DataSource, + _dataSource: DataSource, @Volatile override var defaultRepetitionAttempts: Int = DEFAULT_REPETITION_ATTEMPTS ) : DataSourceTransactionManager(_dataSource), TransactionManager { @@ -40,7 +40,7 @@ class SpringTransactionManager( override fun doBegin(transaction: Any, definition: TransactionDefinition) { super.doBegin(transaction, definition) - if (TransactionSynchronizationManager.hasResource(_dataSource)) { + if (TransactionSynchronizationManager.hasResource(obtainDataSource())) { currentOrNull() ?: initTransaction() } if (!TransactionSynchronizationManager.hasResource(springTxKey)) { @@ -50,8 +50,9 @@ class SpringTransactionManager( override fun doCleanupAfterCompletion(transaction: Any) { super.doCleanupAfterCompletion(transaction) - if (!TransactionSynchronizationManager.hasResource(_dataSource)) { + if (!TransactionSynchronizationManager.hasResource(obtainDataSource())) { TransactionSynchronizationManager.unbindResourceIfPossible(this) + TransactionSynchronizationManager.unbindResource(springTxKey) } if (TransactionSynchronizationManager.isSynchronizationActive() && TransactionSynchronizationManager.getSynchronizations().isEmpty()) { TransactionSynchronizationManager.clearSynchronization() @@ -89,7 +90,7 @@ class SpringTransactionManager( } private fun initTransaction(): Transaction { - val connection = (TransactionSynchronizationManager.getResource(_dataSource) as ConnectionHolder).connection + val connection = (TransactionSynchronizationManager.getResource(obtainDataSource()) as ConnectionHolder).connection val transactionImpl = SpringTransaction(JdbcConnectionImpl(connection), db, defaultIsolationLevel, currentOrNull()) TransactionManager.resetCurrent(this) @@ -120,11 +121,7 @@ class SpringTransactionManager( ) : TransactionInterface { override fun commit() { - connection.run { - if (!autoCommit) { - commit() - } - } + connection.commit() } override fun rollback() { @@ -135,7 +132,6 @@ class SpringTransactionManager( if (TransactionSynchronizationManager.isActualTransactionActive()) { TransactionSynchronizationManager.getResource(springTxKey)?.let { springTx -> this@SpringTransactionManager.doCleanupAfterCompletion(springTx) - TransactionSynchronizationManager.unbindResource(springTxKey) } } }