在 JUnit/Spring 测试期间触发 Hibernate 的 SchemaExport.execute (hbm2ddl.auto) 的原因是啥?

Posted

技术标签:

【中文标题】在 JUnit/Spring 测试期间触发 Hibernate 的 SchemaExport.execute (hbm2ddl.auto) 的原因是啥?【英文标题】:What is responsible for triggering a Hibernate's SchemaExport.execute (hbm2ddl.auto) during a JUnit/Spring test?在 JUnit/Spring 测试期间触发 Hibernate 的 SchemaExport.execute (hbm2ddl.auto) 的原因是什么? 【发布时间】:2014-07-01 20:29:27 【问题描述】:

在使用SpringJUnit4ClassRunner 运行 JUnit/Spring 测试期间,负责触发 Hibernate 的 SchemaExport.execute(考虑到 hbm2ddl.auto 已激活)的 Spring 事件是什么?

(考虑到所有这些,我可以拥有多个具有相同上下文配置的测试套件,并且可以缓存 spring 上下文...)

我问这个问题是因为我设置了一个 Spring 监听器(用作数据填充器),如下所示:

@Profile( Profiles.DEFAULT, Profiles.CLOUD, Profiles.TEST, Profiles.DEV )
@Component
public class BootstrapLoaderListener implements ApplicationListener<ContextRefreshedEvent>, ResourceLoaderAware, Ordered 

    private static final Logger log = Logger.getLogger(BootstrapLoaderListener.class);

    @Override
    public int getOrder() 
        return HIGHEST_PRECEDENCE;
    

    @Override
    @Transactional
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) 
        initApplication();
    
    ...

我有两个集成测试套件 (SpringJUnit4ClassRunner),它们共享相同的上下文配置,因此共享相同的缓存应用程序上下文。

但我注意到,当两个测试套件中的第二个运行第一个之后,上面的应用程序侦听器没有收到通知。

总结一下:

第一个测试套件运行(从模式导出创建表通知应用程序侦听器) 第二个测试套件运行(仅创建表但没有通知应用程序侦听器)

【问题讨论】:

【参考方案1】:

ContextRefreshedEvent 的 Javadoc 明确指出它是一个...

ApplicationContext 初始化或刷新时引发的事件。

因此,您的 BootstrapLoaderListener 只会在 ApplicationContext 启动(即,被初始化或刷新)时被调用,这只会发生一次:当 Spring TestContext 框架为您的测试加载上下文时。不过,如果您使用的是@DirtiesContext,您的ApplicationContext 可能会被重新创建多次。

换句话说,您目睹的关于您的听众的行为是设计使然,并且是意料之中的。

至于为什么要第二次创建您的架构,这仍然是一个谜。通常这不应该发生。例如,如果您使用 Spring 的 LocalSessionFactoryBeanLocalSessionFactoryBuilder,则这些工厂中的每一个都只会在 ApplicationContext 初始化或刷新时创建一次 Hibernate SessionFactory

也许您有一些非标准配置导致SessionFactory 被多次创建,但根据您提供的信息我无法判断。

但是,如果您希望在每次测试之前调用设置代码(因为它不会自动调用多次),则不建议使用ApplicationListener 设置测试数据库。如果您真的想在测试中使用ApplicationListener,您可以在ApplicationContext 中为您的测试从@Before@BeforeTransaction 方法中触发您自己的ContextRefreshedEvent

作为替代方案(可能是更好的解决方案),您可以通过以下方式之一以编程方式在 @Before@BeforeTransaction 方法中调用 SQL 脚本:

SimpleJdbcTestUtils.executeSqlScript(...) 方法之一(在 Spring 2.5 - 3.x 上) JdbcTestUtils.executeSqlScript(...) 方法之一(在 Spring 4.x 及更高版本上) ScriptUtils.executeSqlScript(...) 方法之一(在 Spring 4.0.3 及更高版本上) AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests 中的executeSqlScript(...) 方法 ResourceDatabasePopulatorDatabasePopulatorUtils

从尚未发布的 Spring 4.1 开始,您可以通过 @Sql 注释以声明方式调用 SQL 脚本。

祝你好运!

Sam(Spring TestContext 框架的作者)

附言您还可以实现自定义 TestExecutionListener 以使用上述相同选项以编程方式调用 SQL 脚本。

【讨论】:

非常感谢 Sam 的详细回复。关于ContextRefreshedEvent,其实和我想的一样。现在关于第二次重新创建模式,这对我来说仍然是一个谜!当我找到问题的原因时,我一定会在这里发表评论。

以上是关于在 JUnit/Spring 测试期间触发 Hibernate 的 SchemaExport.execute (hbm2ddl.auto) 的原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Junit5 / spring boot 中命令执行控制器测试类?

使用 JUnit5+Spring Security 测试 Spring Boot 控制器

为啥我的状态之一在我的测试期间没有被触发

如何使用 vue-test-utils 在单元测试期间触发窗口事件

播放2.6 [Scala]:如何在测试期间访问日志内容?

在 XCode 中的 UI 测试期间无法访问自定义视图