使用 Liquibase 为 Spring Boot 应用程序中的单元测试初始化内存 H2
Posted
技术标签:
【中文标题】使用 Liquibase 为 Spring Boot 应用程序中的单元测试初始化内存 H2【英文标题】:Using Liquibase to initialize in-memory H2 for unit tests in Spring Boot application 【发布时间】:2019-11-30 20:19:32 【问题描述】:我在 Spring JPA 测试中多次使用内存数据库,从未遇到过问题。这一次,我有一个更复杂的架构要初始化,并且该架构必须有一个自定义名称(我们的域模型中的一些实体与特定的目录名称相关联。)因此,出于这个原因,以及确保测试完全同步并且与我们初始化和维护模式的方式一致,我正在尝试在执行 Spring Data JPA 存储库单元测试之前使用 Liquibase 初始化内存中的 H2 数据库。
(注意:我们使用Spring Boot 2.1.3.RELEASE和MySql作为我们的主数据库,H2仅用于测试 .)
我一直在遵循 Spring 参考指南来设置 Liquibase executions on startup。我的 Maven POM 中有以下条目:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
<scope>test</scope>
</dependency>
我的测试文件如下所示:
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = PersistenceTestConfig.class)
@DataJpaTest
public class MyRepositoryTest
@Autowired
private MyRepository myRepository;
@Test
public void someDataAccessTest()
// myRepository method invocation and asserts here...
// ...
应用上下文类:
@EnableJpaRepositories("com.mycompany.myproject")
@EntityScan("com.mycompany.myproject")
public class PersistenceTestConfig
public static void main(String... args)
SpringApplication.run(PersistenceTestConfig.class, args);
根据参考指南,
默认情况下,Liquibase 在您的上下文中自动装配 (@Primary) 数据源并将其用于迁移。如果您需要使用不同的DataSource,您可以创建一个并将其@Bean 标记为@LiquibaseDataSource。如果您这样做并且想要两个数据源,请记住创建另一个并将其标记为@Primary。或者,您可以通过在外部属性中设置 spring.liquibase.[url,user,password] 来使用 Liquibase 的本机数据源。设置 spring.liquibase.url 或 spring.liquibase.user 足以使 Liquibase 使用它自己的 DataSource。如果三个属性中的任何一个都没有设置,将使用其等效 spring.datasource 属性的值。
显然,我希望我的测试使用与 Liquibase 用于初始化数据库的数据源实例相同的数据源实例。因此,起初,我尝试指定 spring.datasource 属性而不提供 spring.liquibase。[url, user, password] 属性 - 假设 Liquibase 将使用默认的主 Spring 数据源:
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS corp
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=validate
# LIQUIBASE (LiquibaseProperties)
spring.liquibase.change-log=classpath:db.changelog.xml
#spring.liquibase.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS corp
#spring.liquibase.user=sa
#spring.liquibase.password=
spring.liquibase.default-schema=CORP
spring.liquibase.drop-first=true
这不起作用,因为 Liquibase 没有找到我必须创建表的 CORP 架构:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liquibase' defined in class path resource [org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguratio n$LiquibaseConfiguration.class]: Invocation of init method failed; nested exception is liquibase.exception.DatabaseException: liquibase.command.CommandExecutionException: liquibase.exception.DatabaseException: liquibase.exception.LockException: liquibase.exception.DatabaseException: Schema "CORP" not found; SQL statement:
CREATE TABLE CORP.DATABASECHANGELOGLOCK (ID INT NOT NULL, LOCKED BOOLEAN NOT NULL, LOCKGRANTED TIMESTAMP, LOCKEDBY VARCHAR(255), CONSTRAINT PK_DATABASECHANGELOGLOCK PRIMARY KEY (ID)) [90079-197] [Failed SQL: CREATE TABLE CORP.DATABASECHANGELOGLOCK (ID INT NOT NULL, LOCKED BOOLEAN NOT NULL, LOCKGRANTED TIMESTAMP, LOCKEDBY VARCHAR(255), CONSTRAINT PK_DATABASECHANGELOGLOCK PRIMARY KEY (ID))]
所以,我取出了明确的 spring.datasource 属性定义,只提供了以下 Liquibase 属性:
spring.jpa.hibernate.ddl-auto=validate
# LIQUIBASE (LiquibaseProperties)
spring.liquibase.change-log=classpath:db.changelog.xml
spring.liquibase.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS corp
spring.liquibase.user=sa
spring.liquibase.password=
spring.liquibase.default-schema=CORP
spring.liquibase.drop-first=true
这导致 Liquibase 任务成功执行,并且似乎在启动时将所有必要的表和数据加载到其 native 数据源中 - 使用提供的更改日志文件。我知道发生这种情况是因为我明确设置了 Liquibase DS 属性,并且根据 Spring 文档,这将导致 Liquibase 使用其自己的本机数据源。我想,出于这个原因,虽然 Liquibase 作业现在成功运行,但测试仍在尝试使用不同的 [Spring 默认?] 数据源,并且数据库模式未通过预测试验证。 (没有找到“corp”模式,没有表。)因此,很明显测试使用的数据源实例与我尝试使用 Liquibase 生成的数据源实例不同。
如何让测试使用 Liquibase 生成的内容?
我尝试的任何方法似乎都不起作用。我怀疑我正在使用的自动配置和显式配置之间存在某种冲突。在这种情况下,@DataJpaTest
是一个好方法吗?我确实想将我的应用程序上下文配置限制为严格的 JPA 测试,这些测试我不需要其他任何东西。
应该很简单...但是我一直无法找到正确的方法,也找不到任何可以清楚地解释如何解决此问题的文档。
非常感谢任何帮助!
【问题讨论】:
【参考方案1】:问题在于您正在使用的@DataJpaTest
。
见Documentation of @DataJpaTest
默认情况下,使用 @DataJpaTest 注释的测试将使用嵌入式内存数据库(替换任何显式或通常自动配置的数据源)。 @AutoConfigureTestDatabase 注解可用于覆盖这些设置。
这意味着您的自动配置的数据源被覆盖,并且不考虑 url spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS corp
你会在日志中找到类似的东西
EmbeddedDataSourceBeanFactoryPostProcessor : Replacing 'dataSource' DataSource bean with embedded version
要修复,请使用:
spring.test.database.replace=none
【讨论】:
感谢您的回复。实际上,在将 'spring.test.database.replace=none' 添加到属性文件(或作为 AutoConfigureTestDatabase 注释的属性)之后,我不再在日志。我看到模式验证在测试启动时仍然立即失败,因为没有为没有明确分配目录的实体找到表(例如@Table(name =“some_name”,catalog =“corp”)。但是,它可能是一个不同的问题:我想,我只需要强制使用“corp”模式作为默认模式...... 你可以试试spring.jpa.properties.hibernate.default_schema
【参考方案2】:
总结解决方案...根据@Lesiak 的建议,我在我的测试类中添加了@AutoConfigureTestDatabase
注释,以覆盖@DataJpaTest
强加的默认数据源的使用。 (很遗憾我错过了 Javadoc 中的明显内容!)测试类现在看起来像这样:
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = PersistenceTestConfig.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:init.sql")
public class MyRepoTest
...
上下文配置:
@EnableJpaRepositories("com.mycompany.myproject")
@EntityScan("com.mycompany.myproject")
public class PersistenceTestConfig
public static void main(String... args)
SpringApplication.run(PersistenceTestConfig.class, args);
我的application.properties
test/resources
:
spring.jpa.hibernate.ddl-auto=none
# adding this line seems to make no difference (perhaps, it targets the default DS, not the one used by Liquibase and tests), but using @Sql to execute 'use corp;' statement before tests works!
# spring.jpa.properties.hibernate.default_schema=corp
# LIQUIBASE (LiquibaseProperties)
spring.liquibase.change-log=classpath:db.changelog.xml
spring.liquibase.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS corp
spring.liquibase.user=sa
spring.liquibase.password=
spring.liquibase.default-schema=CORP
#spring.liquibase.liquibase-tablespace=CORP
spring.liquibase.drop-first=true
init.sql
脚本驻留在/test/resources
中并包含一行:use corp;
。 (这很重要,因为我的一些 JPA 实体已显式映射到 corp
目录,而有些则没有,但在测试中它们都必须在同一个 corp
架构中找到。)
Liquibase 任务成功,我在日志中看到生成了 CORP
架构 - 包含所有表等。没有指向 use corp;
脚本的 @Sql
注释,测试开始但似乎只是可以使用 Spring-Data-JPA 生成的在表上使用 corp.
前缀的查询。也就是说,当为映射到具有显式指定目录的表的实体类生成查询时:@Table(name="my_table", catalog="corp")
。如果测试尝试使用未显式映射到“corp”目录的实体,则会引发 SQL 异常,说明未找到该表 - 就好像它正在某个其他默认模式中查找该表一样。所以,我在测试类中添加了@Sql
注解(如上图),在测试前执行use corp;
语句。那完成了工作。 (注意在配置中添加spring.jpa.properties.hibernate.default_schema=corp
似乎没有任何效果。)
感谢@Lesiak 的帮助!
【讨论】:
以上是关于使用 Liquibase 为 Spring Boot 应用程序中的单元测试初始化内存 H2的主要内容,如果未能解决你的问题,请参考以下文章
Spring boot - 在启动时禁用 Liquibase
Liquibase 和 Spring 如何使用单独的用户进行架构更改
使用 Spring-Boot 启动时的 Liquibase 迁移不起作用