尽管存在“@Transactional”,为什么不回滚这些数据库修改?

Why aren't these database modifications rolled back despite the presence of an `@Transactional`?

Testcontainers写了一个简短的便利扩展:

fun JdbcDatabaseContainer<*>.execute(query:DSLContext.()-> Query){
    val connection = DriverManager.getConnection(this.getJdbcUrl(),this.getUsername(),this.getPassword())
    val create = DSL.using(connection)
    create.query().execute()
}

现在想测试一下。

除了 allDataPresent 测试用例(因为那个测试用例无论如何都是只读的)之外的所有测试用例都被注释 @Transactional

因此,我希望这些修改在测试方法后回滚。

取而代之的是

[ERROR] Failures: 
[ERROR]   InitDataIT.allDataPresent:70 
Expecting:
 <36>
to be equal to:
 <30>
but was not.
[ERROR]   InitDataIT.canInsert:90 
Expecting:
 <6>
to be equal to:
 <1>
but was not.
[ERROR]   InitDataIT.canInsertWithExtension:112 
Expecting:
 <6>
to be equal to:
 <1>
but was not.

每个 @Test 都可以独立运行。所以问题一定出在 @Transactional.

那是为什么呢?更重要的是,我如何获得回滚?

完整测试用例(也尝试注释 class,但没有任何区别):

@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = [InitDataIT.TestContextInitializer::class])
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
open class InitDataIT {
    companion object {
        @JvmStatic
        @Container
        private val dbContainer = MySQLContainer<Nothing>().apply {
            withDatabaseName("test")
            withUsername("root")
            withPassword("")
        }
    }
    object TestContextInitializer: ApplicationContextInitializer<ConfigurableApplicationContext> {
        override fun initialize(applicationContext: ConfigurableApplicationContext) {
            TestPropertyValues.of(
                    "spring.datasource.url=${dbContainer.jdbcUrl}",
                    "spring.datasource.username=${dbContainer.username}",
                    "spring.datasource.password=${dbContainer.password}",
                    "spring.datasource.driver-class-name=${dbContainer.driverClassName}"
            ).applyTo(applicationContext)
        }
    }

    private val create:DSLContext


    @Autowired
    constructor(create:DSLContext){
        this.create = create
    }


    @Test
    fun allDataPresent(){
        //given
        val expectedNumberOfEntries = 30

        val query = create.selectCount()
                .from(CUSTOMERS)

        //when
        val numberOfEntries = query.fetchOne{it.value1()}

        //then
        Assertions.assertThat(numberOfEntries).isEqualTo(expectedNumberOfEntries)
    }

    @Test
    @Transactional
    open fun canInsert(){
        //given
        val insertquery = create.insertInto(CUSTOMERS)
                .columns(CUSTOMERS.FIRSTNAME,CUSTOMERS.LASTNAME,CUSTOMERS.EMAIL, CUSTOMERS.STATUS)
                .values("Alice","Tester","Alice.Tester@somewhere.tt",CustomerStatus.Contacted.name)

        val expectedNumberInOffice2 = 1

        //when
        insertquery.execute()

        //then
        val numberInOffice2 = create.selectCount()
                .from(CUSTOMERS)
                .where(CUSTOMERS.EMAIL.contains("somewhere"))
                .fetchOne{it.value1()}
        assertThat(numberInOffice2).isEqualTo(expectedNumberInOffice2)

    }

    @Test
    @Transactional
    open fun canInsertWithExtension(){
        //given
        dbContainer.execute {
            insertInto(CUSTOMERS)
                    .columns(CUSTOMERS.FIRSTNAME,CUSTOMERS.LASTNAME,CUSTOMERS.EMAIL, CUSTOMERS.STATUS)
                    .values("Alice","Tester","Alice.Tester@somewhere.tt",CustomerStatus.Contacted.name)
        }

        val expectedNumberInOffice2 = 1

        //when
        val numberInOffice2 = create.selectCount()
                .from(CUSTOMERS)
                .where(CUSTOMERS.EMAIL.contains("somewhere"))
                .fetchOne{it.value1()}

        //then
        assertThat(numberInOffice2).isEqualTo(expectedNumberInOffice2)

    }

    @Test
    @Transactional
    open fun insertMultipleWithExtension(){
        //given
        dbContainer.execute {
            insertInto(CUSTOMERS)
                    .columns(CUSTOMERS.FIRSTNAME,CUSTOMERS.LASTNAME,CUSTOMERS.EMAIL, CUSTOMERS.STATUS)
                    .values("Alice","Make","Alice.Make@somewhere.tt", CustomerStatus.Customer.name)
                    .values("Bob","Another","Bob.Another@somewhere.tt", CustomerStatus.ClosedLost.name)
                    .values("Charlie","Integration","Charlie.Integration@somewhere.tt",CustomerStatus.NotContacted.name)
                    .values("Denise","Test","Denise.Test@somewhere.tt",CustomerStatus.Customer.name)
                    .values("Ellie","Now","Ellie.Now@somewhere.tt",CustomerStatus.Contacted.name)
        }

        val expectedNumberInOffice2 = 5

        //when
        val numberInOffice2 = create.selectCount()
                .from(CUSTOMERS)
                .where(CUSTOMERS.EMAIL.contains("somewhere"))
                .fetchOne{it.value1()}

        //then
        assertThat(numberInOffice2).isEqualTo(expectedNumberInOffice2)
    }

}

Spring @Transactional 注释不仅仅神奇地适用于您 DriverManager 创建的 JDBC 连接。您的 dbContainer 对象应该在您的 spring 托管数据源上运行。