From 187c848383269bc6c5eed618556b1fade8ba98d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A1=EC=A7=84=ED=98=81=20=28Ivan=29?= Date: Sun, 2 Jul 2023 16:44:56 +0900 Subject: [PATCH 1/6] feat: fix to support spring multi container --- .../spring/SpringTransactionManager.kt | 55 +++++++++++++------ 1 file changed, 38 insertions(+), 17 deletions(-) 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 46f45cc90e..082d55f27f 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 @@ -38,7 +38,8 @@ class SpringTransactionManager( databaseConfig = databaseConfig ) { this } - @Volatile override var defaultIsolationLevel: Int = -1 + @Volatile + override var defaultIsolationLevel: Int = -1 get() { if (field == -1) { field = Database.getDefaultIsolationLevel(db) @@ -46,29 +47,50 @@ class SpringTransactionManager( return field } - private val springTxKey = "SPRING_TX_KEY" + private val transactionStackKey = "SPRING_TRANSACTION_STACK_KEY" + + private fun getTransactionStack(): List { + return TransactionSynchronizationManager.getResource(transactionStackKey) + ?.let { it as List } + ?: listOf() + } + + private fun setTransactionStack(list: List) { + TransactionSynchronizationManager.unbindResourceIfPossible(transactionStackKey) + TransactionSynchronizationManager.bindResource(transactionStackKey, list) + } + + private fun pushTransactionStack(transaction: TransactionManager) { + val transactionList = getTransactionStack() + setTransactionStack(transactionList + transaction) + } + + private fun popTransactionStack() = setTransactionStack(getTransactionStack().dropLast(1)) + + private fun getLastTransactionStack() = getTransactionStack().lastOrNull() override fun doBegin(transaction: Any, definition: TransactionDefinition) { super.doBegin(transaction, definition) if (TransactionSynchronizationManager.hasResource(obtainDataSource())) { - currentOrNull() ?: initTransaction() - } - if (!TransactionSynchronizationManager.hasResource(springTxKey)) { - TransactionSynchronizationManager.bindResource(springTxKey, transaction) + currentOrNull() ?: initTransaction(transaction) } + + pushTransactionStack(this@SpringTransactionManager) } override fun doCleanupAfterCompletion(transaction: Any) { super.doCleanupAfterCompletion(transaction) if (!TransactionSynchronizationManager.hasResource(obtainDataSource())) { TransactionSynchronizationManager.unbindResourceIfPossible(this) - TransactionSynchronizationManager.unbindResource(springTxKey) } + + popTransactionStack() + TransactionManager.resetCurrent(getLastTransactionStack()) + if (TransactionSynchronizationManager.isSynchronizationActive() && TransactionSynchronizationManager.getSynchronizations().isEmpty()) { TransactionSynchronizationManager.clearSynchronization() } - TransactionManager.resetCurrent(null) } override fun doSuspend(transaction: Any): Any { @@ -100,21 +122,21 @@ class SpringTransactionManager( isolationLevel = isolation } - getTransaction(tDefinition) - - return currentOrNull() ?: initTransaction() + val transactionStatus = (getTransaction(tDefinition) as DefaultTransactionStatus) + return currentOrNull() ?: initTransaction(transactionStatus.transaction) } - private fun initTransaction(): Transaction { + private fun initTransaction(transaction: Any): Transaction { val connection = (TransactionSynchronizationManager.getResource(obtainDataSource()) as ConnectionHolder).connection val transactionImpl = try { - SpringTransaction(JdbcConnectionImpl(connection), db, defaultIsolationLevel, defaultReadOnly, currentOrNull()) + SpringTransaction(JdbcConnectionImpl(connection), db, defaultIsolationLevel, defaultReadOnly, currentOrNull(), transaction) } catch (e: Exception) { exposedLogger.error("Failed to start transaction. Connection will be closed.", e) connection.close() throw e } + TransactionManager.resetCurrent(this) return Transaction(transactionImpl).apply { TransactionSynchronizationManager.bindResource(this@SpringTransactionManager, this) @@ -143,7 +165,8 @@ class SpringTransactionManager( override val db: Database, override val transactionIsolation: Int, override val readOnly: Boolean, - override val outerTransaction: Transaction? + override val outerTransaction: Transaction?, + private val currentTransaction: Any, ) : TransactionInterface { override fun commit() { @@ -156,9 +179,7 @@ class SpringTransactionManager( override fun close() { if (TransactionSynchronizationManager.isActualTransactionActive()) { - TransactionSynchronizationManager.getResource(springTxKey)?.let { springTx -> - this@SpringTransactionManager.doCleanupAfterCompletion(springTx) - } + this@SpringTransactionManager.doCleanupAfterCompletion(currentTransaction) } } } From 32c046da51bb6f90be11edfa3a590c6f46fe55fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A1=EC=A7=84=ED=98=81=20=28Ivan=29?= Date: Sun, 2 Jul 2023 16:45:13 +0900 Subject: [PATCH 2/6] test: add test for spring multi container --- .../SpringMultiContainerTransactionTest.kt | 226 ++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt diff --git a/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt new file mode 100644 index 0000000000..31fe258968 --- /dev/null +++ b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt @@ -0,0 +1,226 @@ +package org.jetbrains.exposed.spring + +import org.jetbrains.exposed.dao.id.LongIdTable +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.deleteAll +import org.jetbrains.exposed.sql.insertAndGetId +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.transactions.transaction +import org.junit.Assert +import org.junit.Test +import org.springframework.context.annotation.AnnotationConfigApplicationContext +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType +import org.springframework.transaction.annotation.EnableTransactionManagement +import org.springframework.transaction.annotation.Transactional +import javax.sql.DataSource +import kotlin.test.BeforeTest + +/** + * @author ivan@daangn.com + */ +open class SpringMultiContainerTransactionTest { + + val orderContainer = AnnotationConfigApplicationContext(OrderConfig::class.java) + val paymentContainer = AnnotationConfigApplicationContext(PaymentConfig::class.java) + + val orders: Orders = orderContainer.getBean(Orders::class.java) + val payments: Payments = paymentContainer.getBean(Payments::class.java) + + @BeforeTest + open fun beforeTest() { + orders.init() + payments.init() + } + + @Test + open fun test1() { + Assert.assertEquals(0, orders.findAll().size) + Assert.assertEquals(0, payments.findAll().size) + } + + @Test + open fun test2() { + orders.create() + Assert.assertEquals(1, orders.findAll().size) + payments.create() + Assert.assertEquals(1, payments.findAll().size) + } + + @Test + open fun test3() { + orders.databaseTemplate { + payments.create() + orders.create() + payments.create() + } + Assert.assertEquals(1, orders.findAll().size) + Assert.assertEquals(2, payments.findAll().size) + } + + @Test + open fun test4() { + kotlin.runCatching { + orders.databaseTemplate { + orders.create() + payments.create() + throw Error() + } + } + Assert.assertEquals(0, orders.findAll().size) + Assert.assertEquals(1, payments.findAll().size) + } + + @Test + open fun test5() { + kotlin.runCatching { + orders.databaseTemplate { + orders.create() + payments.databaseTemplate { + payments.create() + throw Error() + } + } + } + Assert.assertEquals(0, orders.findAll().size) + Assert.assertEquals(0, payments.findAll().size) + } + + @Test + open fun test6() { + Assert.assertEquals(0, orders.findAllWithExposedTrxBlock().size) + Assert.assertEquals(0, payments.findAllWithExposedTrxBlock().size) + } + + @Test + open fun test7() { + orders.createWithExposedTrxBlock() + Assert.assertEquals(1, orders.findAllWithExposedTrxBlock().size) + payments.createWithExposedTrxBlock() + Assert.assertEquals(1, payments.findAllWithExposedTrxBlock().size) + } + + @Test + open fun test8() { + orders.databaseTemplate { + payments.createWithExposedTrxBlock() + orders.createWithExposedTrxBlock() + payments.createWithExposedTrxBlock() + } + Assert.assertEquals(1, orders.findAllWithExposedTrxBlock().size) + Assert.assertEquals(2, payments.findAllWithExposedTrxBlock().size) + } + + @Test + open fun test9() { + kotlin.runCatching { + orders.databaseTemplate { + orders.createWithExposedTrxBlock() + payments.createWithExposedTrxBlock() + throw Error() + } + } + Assert.assertEquals(0, orders.findAllWithExposedTrxBlock().size) + Assert.assertEquals(1, payments.findAllWithExposedTrxBlock().size) + } + + @Test + open fun test10() { + kotlin.runCatching { + orders.databaseTemplate { + orders.createWithExposedTrxBlock() + payments.databaseTemplate { + payments.createWithExposedTrxBlock() + throw Error() + } + } + } + Assert.assertEquals(0, orders.findAllWithExposedTrxBlock().size) + Assert.assertEquals(0, payments.findAllWithExposedTrxBlock().size) + } +} + +@Configuration +@EnableTransactionManagement(proxyTargetClass = true) +open class OrderConfig { + + @Bean + open fun dataSource(): EmbeddedDatabase = EmbeddedDatabaseBuilder().setName("embeddedTest1").setType(EmbeddedDatabaseType.H2).build() + + @Bean + open fun transactionManager(dataSource: DataSource) = SpringTransactionManager(dataSource) + + @Bean + open fun orders() = Orders() +} + +@Transactional +open class Orders { + + open fun findAll() = Order.selectAll().map { it } + + open fun findAllWithExposedTrxBlock() = transaction { findAll() } + + open fun create() = Order.insertAndGetId { + it[buyer] = 123 + }.value + + open fun createWithExposedTrxBlock() = transaction { create() } + + open fun init() { + SchemaUtils.create(Order) + Order.deleteAll() + } + + open fun databaseTemplate(block: () -> Unit) { + block() + } +} + +object Order : LongIdTable("orders") { + val buyer = long("buyer_id") +} + +@Configuration +@EnableTransactionManagement(proxyTargetClass = true) +open class PaymentConfig { + + @Bean + open fun dataSource(): EmbeddedDatabase = EmbeddedDatabaseBuilder().setName("embeddedTest2").setType(EmbeddedDatabaseType.H2).build() + + @Bean + open fun transactionManager(dataSource: DataSource) = SpringTransactionManager(dataSource) + + @Bean + open fun payments() = Payments() +} + +@Transactional +open class Payments { + + open fun findAll() = Payment.selectAll().map { it } + + open fun findAllWithExposedTrxBlock() = transaction { findAll() } + + open fun create() = Payment.insertAndGetId { + it[state] = "state" + }.value + + open fun createWithExposedTrxBlock() = transaction { create() } + + open fun init() { + SchemaUtils.create(Payment) + Payment.deleteAll() + } + + open fun databaseTemplate(block: () -> Unit) { + block() + } +} + +object Payment : LongIdTable("payments") { + val state = varchar("state", 50) +} From 6b23344f22dba25624d1e853be5aeacec36a91e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A1=EC=A7=84=ED=98=81=20=28Ivan=29?= Date: Tue, 18 Jul 2023 21:30:30 +0900 Subject: [PATCH 3/6] Fix test --- .../SpringMultiContainerTransactionTest.kt | 18 ++++---- .../spring/SpringTransactionEntityTest.kt | 45 +++++++++++++++---- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt index 31fe258968..a8eb05d855 100644 --- a/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt +++ b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt @@ -52,7 +52,7 @@ open class SpringMultiContainerTransactionTest { @Test open fun test3() { - orders.databaseTemplate { + orders.transaction { payments.create() orders.create() payments.create() @@ -64,7 +64,7 @@ open class SpringMultiContainerTransactionTest { @Test open fun test4() { kotlin.runCatching { - orders.databaseTemplate { + orders.transaction { orders.create() payments.create() throw Error() @@ -77,7 +77,7 @@ open class SpringMultiContainerTransactionTest { @Test open fun test5() { kotlin.runCatching { - orders.databaseTemplate { + orders.transaction { orders.create() payments.databaseTemplate { payments.create() @@ -105,7 +105,7 @@ open class SpringMultiContainerTransactionTest { @Test open fun test8() { - orders.databaseTemplate { + orders.transaction { payments.createWithExposedTrxBlock() orders.createWithExposedTrxBlock() payments.createWithExposedTrxBlock() @@ -117,7 +117,7 @@ open class SpringMultiContainerTransactionTest { @Test open fun test9() { kotlin.runCatching { - orders.databaseTemplate { + orders.transaction { orders.createWithExposedTrxBlock() payments.createWithExposedTrxBlock() throw Error() @@ -130,7 +130,7 @@ open class SpringMultiContainerTransactionTest { @Test open fun test10() { kotlin.runCatching { - orders.databaseTemplate { + orders.transaction { orders.createWithExposedTrxBlock() payments.databaseTemplate { payments.createWithExposedTrxBlock() @@ -162,20 +162,20 @@ open class Orders { open fun findAll() = Order.selectAll().map { it } - open fun findAllWithExposedTrxBlock() = transaction { findAll() } + open fun findAllWithExposedTrxBlock() = org.jetbrains.exposed.sql.transactions.transaction { findAll() } open fun create() = Order.insertAndGetId { it[buyer] = 123 }.value - open fun createWithExposedTrxBlock() = transaction { create() } + open fun createWithExposedTrxBlock() = org.jetbrains.exposed.sql.transactions.transaction { create() } open fun init() { SchemaUtils.create(Order) Order.deleteAll() } - open fun databaseTemplate(block: () -> Unit) { + open fun transaction(block: () -> Unit) { block() } } diff --git a/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringTransactionEntityTest.kt b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringTransactionEntityTest.kt index 7718696539..1dfc0b4988 100644 --- a/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringTransactionEntityTest.kt +++ b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringTransactionEntityTest.kt @@ -5,12 +5,16 @@ import org.jetbrains.exposed.dao.UUIDEntityClass import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.transaction import org.junit.Test import org.springframework.beans.factory.annotation.Autowired +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase import org.springframework.test.annotation.Commit import org.springframework.transaction.annotation.Transactional import java.util.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -39,6 +43,11 @@ class OrderDAO(id: EntityID) : UUIDEntity(id) { @org.springframework.stereotype.Service @Transactional open class Service { + + open fun init() { + SchemaUtils.create(CustomerTable, OrderTable) + } + open fun createCustomer(name: String): CustomerDAO { return CustomerDAO.new { this.name = name @@ -59,6 +68,14 @@ open class Service { open fun findOrderByProduct(product: String): OrderDAO? { return OrderDAO.find { OrderTable.product eq product }.singleOrNull() } + + open fun transaction(block: () -> Unit) { + block() + } + + open fun cleanUp() { + SchemaUtils.drop(CustomerTable, OrderTable) + } } open class SpringTransactionEntityTest : SpringTransactionTestBase() { @@ -66,29 +83,39 @@ open class SpringTransactionEntityTest : SpringTransactionTestBase() { @Autowired lateinit var service: Service - @Test @Commit - open fun test01() { - transaction { - SchemaUtils.create(CustomerTable, OrderTable) - } + @Autowired + lateinit var ds: EmbeddedDatabase + + @BeforeTest + open fun beforeTest() { + service.init() + } + @Test + @Commit + open fun test01() { val customer = service.createCustomer("Alice1") service.createOrder(customer, "Product1") val order = service.findOrderByProduct("Product1") assertNotNull(order) - transaction { + service.transaction { assertEquals("Alice1", order.customer.name) } } - @Test @Commit + @Test + @Commit fun test02() { service.doBoth("Bob", "Product2") val order = service.findOrderByProduct("Product2") assertNotNull(order) - transaction { + service.transaction { assertEquals("Bob", order.customer.name) - SchemaUtils.drop(CustomerTable, OrderTable) } } + + @AfterTest + fun afterTest() { + service.cleanUp() + } } From 7fb3c9271cb6814220192fba8f0b0411c1a498c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A1=EC=A7=84=ED=98=81=20=28Ivan=29?= Date: Thu, 20 Jul 2023 10:30:10 +0900 Subject: [PATCH 4/6] chore: remove unused variable --- .../jetbrains/exposed/spring/SpringTransactionEntityTest.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringTransactionEntityTest.kt b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringTransactionEntityTest.kt index 1dfc0b4988..c5387eb4ca 100644 --- a/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringTransactionEntityTest.kt +++ b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringTransactionEntityTest.kt @@ -83,9 +83,6 @@ open class SpringTransactionEntityTest : SpringTransactionTestBase() { @Autowired lateinit var service: Service - @Autowired - lateinit var ds: EmbeddedDatabase - @BeforeTest open fun beforeTest() { service.init() From 7380ee376ee8d781d1c7b1bd519d8abbb12a420a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A1=EC=A7=84=ED=98=81=20=28Ivan=29?= Date: Thu, 20 Jul 2023 20:27:50 +0900 Subject: [PATCH 5/6] chore: fix test to apply detekt --- .../spring/SpringMultiContainerTransactionTest.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt index a8eb05d855..3e2904d098 100644 --- a/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt +++ b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt @@ -19,9 +19,6 @@ import org.springframework.transaction.annotation.Transactional import javax.sql.DataSource import kotlin.test.BeforeTest -/** - * @author ivan@daangn.com - */ open class SpringMultiContainerTransactionTest { val orderContainer = AnnotationConfigApplicationContext(OrderConfig::class.java) @@ -67,7 +64,7 @@ open class SpringMultiContainerTransactionTest { orders.transaction { orders.create() payments.create() - throw Error() + throw SpringTransactionTestException() } } Assert.assertEquals(0, orders.findAll().size) @@ -81,7 +78,7 @@ open class SpringMultiContainerTransactionTest { orders.create() payments.databaseTemplate { payments.create() - throw Error() + throw SpringTransactionTestException() } } } @@ -120,7 +117,7 @@ open class SpringMultiContainerTransactionTest { orders.transaction { orders.createWithExposedTrxBlock() payments.createWithExposedTrxBlock() - throw Error() + throw SpringTransactionTestException() } } Assert.assertEquals(0, orders.findAllWithExposedTrxBlock().size) @@ -134,7 +131,7 @@ open class SpringMultiContainerTransactionTest { orders.createWithExposedTrxBlock() payments.databaseTemplate { payments.createWithExposedTrxBlock() - throw Error() + throw SpringTransactionTestException() } } } @@ -224,3 +221,5 @@ open class Payments { object Payment : LongIdTable("payments") { val state = varchar("state", 50) } + +private class SpringTransactionTestException() : Error() From b5fed98cfea7ec79efafa099e7197d7e4e602d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A1=EC=A7=84=ED=98=81=20=28Ivan=29?= Date: Thu, 20 Jul 2023 20:53:13 +0900 Subject: [PATCH 6/6] chore: remove unused imports and empty construct --- .../exposed/spring/SpringMultiContainerTransactionTest.kt | 2 +- .../jetbrains/exposed/spring/SpringTransactionEntityTest.kt | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt index 3e2904d098..16980c13d9 100644 --- a/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt +++ b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringMultiContainerTransactionTest.kt @@ -222,4 +222,4 @@ object Payment : LongIdTable("payments") { val state = varchar("state", 50) } -private class SpringTransactionTestException() : Error() +private class SpringTransactionTestException : Error() diff --git a/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringTransactionEntityTest.kt b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringTransactionEntityTest.kt index c5387eb4ca..959971aa87 100644 --- a/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringTransactionEntityTest.kt +++ b/spring-transaction/src/test/kotlin/org/jetbrains/exposed/spring/SpringTransactionEntityTest.kt @@ -5,11 +5,8 @@ import org.jetbrains.exposed.dao.UUIDEntityClass import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.transactions.TransactionManager -import org.jetbrains.exposed.sql.transactions.transaction import org.junit.Test import org.springframework.beans.factory.annotation.Autowired -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase import org.springframework.test.annotation.Commit import org.springframework.transaction.annotation.Transactional import java.util.*