spring-boot - 尽管存在 `@Transactional` ,为什么这些数据库修改没有回滚?

标签 spring-boot junit integration-testing jooq testcontainers

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()
}

现在想测试一下。

  • Flyway 加载 30 个条目。这些应该在 allDataPresent
  • 中可见
  • canInsert 插入一个不带扩展名的条目
  • canInsertWithExtension 执行相同的操作,但通过扩展函数
  • insertMultipleWithExtension 正如其名称所暗示的那样,插入另一个 5

除了 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 上。

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

完整的测试用例(也尝试对类进行注释,但没有任何区别):

@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","<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="eeaf82878d8bc0ba8b9d9a8b9cae9d81838b99868b9c8bc09a9a" rel="noreferrer noopener nofollow">[email protected]</a>",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","<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="cd8ca1a4aea8e399a8beb9a8bf8dbea2a0a8baa5a8bfa8e3b9b9" rel="noreferrer noopener nofollow">[email protected]</a>",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","<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="8ccde0e5efe9a2c1ede7e9ccffe3e1e9fbe4e9fee9a2f8f8" rel="noreferrer noopener nofollow">[email protected]</a>", CustomerStatus.Customer.name)
                    .values("Bob","Another","<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="aeecc1cc80efc0c1dac6cbdceeddc1c3cbd9c6cbdccb80dada" rel="noreferrer noopener nofollow">[email protected]</a>", CustomerStatus.ClosedLost.name)
                    .values("Charlie","Integration","<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="397a51584b55505c1770574d5c5e4b584d505657794a56545c4e515c4b5c174d4d" rel="noreferrer noopener nofollow">[email protected]</a>",CustomerStatus.NotContacted.name)
                    .values("Denise","Test","<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0f4b6a61667c6a215b6a7c7b4f7c60626a78676a7d6a217b7b" rel="noreferrer noopener nofollow">[email protected]</a>",CustomerStatus.Customer.name)
                    .values("Ellie","Now","<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="71341d1d18145f3f1e0631021e1c1406191403145f0505" rel="noreferrer noopener nofollow">[email protected]</a>",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 管理的数据源上运行。

关于spring-boot - 尽管存在 `@Transactional` ,为什么这些数据库修改没有回滚?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55634713/

相关文章:

java - Spring Boot 1.4 - 如何使用验证来测试 Controller

python - 使用参数化测试或单独测试的测试工作流程

Spring STOMP 不完整框架

java - 如何在 Android 单元测试中调用测试监听器接口(interface)

java - 当计时器调用委托(delegate)时,Camunda MockExpressionManager 不起作用

java - 依赖问题 : Java SE-1. 8、JUnit 5、Mockito 4.0 和 PowerMock

svn - 如何禁止修改版本控制系统中的所有文件

spring - 如何设计具有外部阻塞 API 调用的响应式(Reactive)微服务?

java - 数据库对象POJO中的文件(Springboot)

spring - org.thymeleaf.exceptions.TemplateInputException 异常