简体   繁体   English

带有 testcontainers 的 Spring Boot - 如何在上下文重新加载时防止数据库初始化

[英]Spring boot with testcontainers - how to prevent DB initialization on context reload

Context语境


I have a suite of Integration tests in a Spring boot application.我在 Spring 启动应用程序中有一套集成测试。 The test context uses a MSSQL docker container for it's database using the testcontainers framework.测试上下文使用一个 MSSQL docker容器作为它的数据库,使用testcontainers框架。

Some of my tests use Mockito with SpyBean which, apparently by design , will restart the Spring context since the spied beans cannot be shared between tests.我的一些测试将 Mockito 与 SpyBean 一起使用,显然按照设计,这将重新启动 Spring 上下文,因为无法在测试之间共享被监视的 bean。

Since I am using a non-embedded database that lives for the duration of all my tests, the database is provisioned by executing my schema.sql and data.sql at the start by using:-由于我使用的是在所有测试期间都存在的非嵌入式数据库,因此通过在开始时执行我的 schema.sql 和 data.sql 来配置数据库:-

spring.datasource.initialization-mode=always

The problem is that when the Spring context is restarted, my database is re-initialized again which triggers errors such as unique constraint issues, table already exists etc.问题是,当 Spring 上下文重新启动时,我的数据库再次重新初始化,这会触发诸如唯一约束问题、表已存在等错误。

My parent test class is as follows if it's of any help:-如果有任何帮助,我的父测试类如下:-

@ActiveProfiles(Profiles.PROFILE_TEST)
@Testcontainers
@SpringJUnitWebConfig
@AutoConfigureMockMvc
@SpringBootTest(classes = Application.class)
@ContextConfiguration(initializers = {IntegrationTest.Initializer.class})
public abstract class IntegrationTest {

    private static final MSSQLServerContainer<?> mssqlContainer;

    static {
        mssqlContainer = new MSSQLServerContainer<>()
                .withInitScript("setup.sql"); //Creates users/permissions etc
        mssqlContainer.start();
    }

    static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        @Override
        public void initialize(final ConfigurableApplicationContext configurableApplicationContext) {
            TestPropertyValues.of("spring.datasource.url=" + mssqlContainer.getJdbcUrl())
                    .applyTo(configurableApplicationContext.getEnvironment());
        }
    }
}

Each integration test extends this so that the context (for non-spied tests) is shared and setup occurs just once.每个集成测试都扩展了这一点,以便共享上下文(对于非间谍测试)并且设置只发生一次。

What I want我想要的是


I would like to be able to execute the startup scripts just one time on startup and never again despite any number of context reloads.我希望能够在启动时只执行一次启动脚本,而不管有多少上下文重新加载,我都不会再执行一次。 If the Spring test framework could remember that I already have a provisioned DB, that would be ideal.如果 Spring 测试框架可以记住我已经有一个已配置的数据库,那将是理想的。

I am wondering if there are any existing configurations or hooks that may help me我想知道是否有任何现有的配置或挂钩可以帮助我

If something like the following existed, it'd be perfect.如果存在类似以下内容,那就完美了。

spring.datasource.initialization-mode=always-once

But, as far as I can tell, it doesn't :(但是,据我所知,它没有:(

Possible, but incomplete, solutions可能但不完整的解决方案


  1. Test container init script测试容器初始化脚本
new MSSQLServerContainer<>().withInitScript("setup.sql");

This works and ensures I can run a startup script the first time only since the container is started up just once.这有效并确保我可以第一次运行启动脚本,因为容器只启动了一次。 However withInitScript only takes a single argument rather than an array.然而withInitScript只接受一个参数而不是一个数组。 As such, I would need to concatenate all my scripts into one file which means I'd have to maintain two sets of scripts.因此,我需要将所有脚本连接到一个文件中,这意味着我必须维护两组脚本。

If you only had one script, this would work fine.如果你只有一个脚本,这会很好用。

  1. Continue on error继续出错
spring.datasource.continue-on-error=true

This works in the sense that startup errors in the schema are ignored.这在忽略模式中的启动错误的意义上起作用。 But.. I want it to fail on startup if someone put some dodgy SQL in the scripts.但是.. 如果有人在脚本中加入了一些狡猾的 SQL,我希望它在启动时失败。

  1. Spring event hooks Spring 事件钩子

I couldn't get this to work.我无法让这个工作。 My idea was that I could listen for the ContextRefreshedEvent and then inject a new value for spring.datasource.initialization-mode=never .我的想法是我可以监听ContextRefreshedEvent然后为spring.datasource.initialization-mode=never注入一个新值。

It's a bit of a hack but I tried something like the following这有点像黑客,但我尝试了以下内容

    @Component
    public static class EventListener implements ApplicationListener<ApplicationEvent> {

        @Autowired
        private ConfigurableEnvironment environment;

        @Override
        public void onApplicationEvent(final ApplicationEvent event) {
            log.info(event.getClass().getSimpleName());
            if (event instanceof ContextRefreshedEvent) {
                TestPropertyValues.of("spring.datasource.initialization-mode=never")
                        .applyTo(this.environment);
            }
        }
    }

My guess is when the context restarts, it will also reload all my original property sources again which has mode=always .我的猜测是,当上下文重新启动时,它还会重新加载我所有具有mode=always 的原始属性源。 I would need an event right after the properties are loaded and right before the schema creation occurs.我需要在属性加载之后和模式创建发生之前的事件。

So with that, does anyone have any suggestions?那么,有没有人有任何建议?

So I ended up finding a workaround for this.所以我最终找到了一个解决方法。 Feels hacky but unless someone else is able to suggest a more appropriate and less obscure fix, then this is what I'll go with.感觉很糟糕,但除非其他人能够提出更合适且不那么晦涩的解决方案,否则这就是我将采用的方法。

The solution uses a combination of @tsarenkotxt suggestion of AtomicBoolean and my #3 partial solution.该解决方案结合了@tsarenkotxt 的 AtomicBoolean 建议和我的 #3 部分解决方案。



    @ActiveProfiles(Profiles.PROFILE_TEST)
    @Testcontainers
    @SpringJUnitWebConfig
    @AutoConfigureMockMvc
    @SpringBootTest(classes = Application.class)
    @ContextConfiguration(initializers = {IntegrationTest.Initializer.class})
    public abstract class IntegrationTest {

        private static final MSSQLServerContainer mssqlContainer;

        //added this
        private static final AtomicBoolean initDB = new AtomicBoolean(true);

        static {
            mssqlContainer = new MSSQLServerContainer()
                    .withInitScript("setup.sql"); //Creates users/permissions etc
            mssqlContainer.start();
        }

        static class Initializer implements ApplicationContextInitializer {

            @Override
            public void initialize(final ConfigurableApplicationContext configurableApplicationContext) {
                TestPropertyValues.of(
                    "spring.datasource.url=" + mssqlContainer.getJdbcUrl(),

                    //added this
                    "spring.datasource.initialization-mode=" + (initDB.get() ? "always" : "never"))
                .applyTo(configurableApplicationContext.getEnvironment());

                //added this
                initDB.set(false);
            }
        }
    }

Basically I set spring.datasource.initialization-mode to be always on the very first startup, since the db hasn't been setup yet, and then reset it to never for every context initialization thereafter.基本上,我将spring.datasource.initialization-mode设置为始终在第一次启动时,因为尚未设置数据库,然后在此后的每个上下文初始化中将其重置为从不 As such, Spring won't attempt to execute the startup scripts after the first run.因此,Spring 不会在第一次运行后尝试执行启动脚本。

Works great but I don't like having to hide this configuration here so still hoping someone else will come up with something better and more "by design"效果很好,但我不喜欢在这里隐藏这个配置,所以仍然希望其他人能提出更好、更多的“设计”

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Spring Boot中的上下文初始化问题 - context initialization Issue in Spring Boot 如何解决 Spring Boot 上的 org.testcontainers.containers.ContainerFetchException? - How to resolve org.testcontainers.containers.ContainerFetchException on Spring Boot? 如何在 spring 启动集成测试期间正确连接到测试容器 redis? - How to connect to testcontainers redis correctly during spring boot integration test? spring boot重启时如何防止db初始化 - How to prevent db from initialize when spring boot restart Spring 使用 testcontainers 启动测试 postgresql - Spring boot test using testcontainers postgresql Spring Boot + Testcontainers 与远程 Liquidbase 迁移 - Spring Boot + Testcontainers with remote liquidbase migrations Spring 引导 - 上下文初始化期间遇到异常。 如何知道错误的原因? - Spring Boot - Exception encountered during context initialization. How to know the reason of error? 是否可以在 Spring(非引导)上使用 Testcontainers? - Is it possible to use Testcontainers on Spring (non Boot)? Spring 启动项目:上下文初始化期间遇到异常 - Spring Boot project : Exception encountered during context initialization 如何控制Spring上下文初始化错误 - How to control Spring context initialization errors
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM