使用 r2dbc 和 flyway 在 spring 启动应用程序中设置 h2

Setup h2 in spring boot application with r2dbc and flyway

我正在玩 Spring Boot 和名为 r2dbc 的反应式 jdbc 驱动程序。在我的主要应用程序中,我使用 Postgres 作为数据库,现在我想使用 h2 进行测试。 Flyway 迁移正在使用设置,但是当 Spring 应用程序能够插入记录时。

这是我的设置和代码

@SpringBootTest
class CustomerRepositoryTest {

    @Autowired
    CustomerRepository repository;

    @Test
    void insertToDatabase() {
        repository.saveAll(List.of(new Customer("Jack", "Bauer"),
                new Customer("Chloe", "O'Brian"),
                new Customer("Kim", "Bauer"),
                new Customer("David", "Palmer"),
                new Customer("Michelle", "Dessler")))
                .blockLast(Duration.ofSeconds(10));
    }
}

这是我收到的错误

 :: Spring Boot ::        (v2.3.4.RELEASE)

2020-10-14 15:59:18.538  INFO 25279 --- [           main] i.g.i.repository.CustomerRepositoryTest  : Starting CustomerRepositoryTest on imalik8088.fritz.box with PID 25279 (started by imalik in /Users/imalik/code/private/explore-java/spring-example)
2020-10-14 15:59:18.540  INFO 25279 --- [           main] i.g.i.repository.CustomerRepositoryTest  : No active profile set, falling back to default profiles: default
2020-10-14 15:59:19.108  INFO 25279 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
2020-10-14 15:59:19.273  INFO 25279 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 160ms. Found 1 R2DBC repository interfaces.
2020-10-14 15:59:19.894  INFO 25279 --- [           main] o.f.c.internal.license.VersionPrinter    : Flyway Community Edition 6.5.0 by Redgate
2020-10-14 15:59:20.052  INFO 25279 --- [           main] o.f.c.internal.database.DatabaseFactory  : Database: jdbc:h2:mem:///DBNAME (H2 1.4)
2020-10-14 15:59:20.118  INFO 25279 --- [           main] o.f.core.internal.command.DbValidate     : Successfully validated 1 migration (execution time 00:00.022s)
2020-10-14 15:59:20.131  INFO 25279 --- [           main] o.f.c.i.s.JdbcTableSchemaHistory         : Creating Schema History table "PUBLIC"."flyway_schema_history" ...
2020-10-14 15:59:20.175  INFO 25279 --- [           main] o.f.core.internal.command.DbMigrate      : Current version of schema "PUBLIC": << Empty Schema >>
2020-10-14 15:59:20.178  INFO 25279 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version 1.0.0 - schma
2020-10-14 15:59:20.204  INFO 25279 --- [           main] o.f.core.internal.command.DbMigrate      : Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.036s)
2020-10-14 15:59:20.689  INFO 25279 --- [           main] i.g.i.repository.CustomerRepositoryTest  : Started CustomerRepositoryTest in 2.466 seconds (JVM running for 3.326)

2020-10-14 15:59:21.115 DEBUG 25279 --- [           main] o.s.d.r2dbc.core.DefaultDatabaseClient   : Executing SQL statement [INSERT INTO customer (first_name, last_name) VALUES (, )]


org.springframework.data.r2dbc.BadSqlGrammarException: executeMany; bad SQL grammar [INSERT INTO customer (first_name, last_name) VALUES (, )]; nested exception is io.r2dbc.spi.R2dbcBadGrammarException: [42102] [42S02] Tabelle "CUSTOMER" nicht gefunden
Table "CUSTOMER" not found; SQL statement:
INSERT INTO customer (first_name, last_name) VALUES (, ) [42102-200]

我的 src/test/resources/application.yaml 看起来像这样:

spring:
  r2dbc:
    url: r2dbc:h2:mem:///DBNAME?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:

  flyway:
    url: jdbc:h2:mem:///DBNAME
    baseline-on-migrate: true
    user: sa
    password:

有什么想法遗漏了什么或设置有什么问题吗?如果需要更多信息,请告诉我。

Addition/Solution:

url 模式在 jdbc 和 r2dbc 之间是不同的。我的工作解决方案如下:

url: r2dbc:h2:file:///./tmp/test-database
url: jdbc:h2:file:./tmp/test-database

并且为了设置 Flyway,您必须配置 Flyway:

// Flyway is not compatible with r2dbc yet, therefore this config class is created
@Configuration
public class FlywayConfig {

    private final Environment env;

    public FlywayConfig(final Environment env) {
        this.env = env;
    }

    @Bean(initMethod = "migrate")
    public Flyway flyway() {
        return new Flyway(Flyway.configure()
                .baselineOnMigrate(true)
                .dataSource(
                        env.getRequiredProperty("spring.flyway.url"),
                        env.getRequiredProperty("spring.flyway.user"),
                        env.getRequiredProperty("spring.flyway.password"))
        );
    }
}

我目前在将 r2dbc 与 liquibase 结合使用时遇到同样的问题。我怀疑 JDBC url 指向不同的数据库,因为 R2DB 和 JDBC 之间的语法略有不同。不过,我可以设法从文件系统中获取 h2 运行...

    url: r2dbc:h2:file:///~/db/testdb
...
    url: jdbc:h2:file:~/db/testdb

编辑:

在 non-reactive Spring 数据中,我通常使用模式将模式填充到 H2 内存数据库中。sql/data.sql 对。使用 R2DBC 也可以做到这一点,但是您必须自己配置填充器。

它也在 Getting Started R2DBC Tutorial 中。基本上你必须注册一个 ConnectionFactoryInitializer bean。

  @Bean
  public ConnectionFactoryInitializer initializer(@Qualifier("connectionFactory") ConnectionFactory connectionFactory) {
    var initializer = new ConnectionFactoryInitializer();
    initializer.setConnectionFactory(connectionFactory);

    var populator = new CompositeDatabasePopulator();
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("data.sql")));
    initializer.setDatabasePopulator(populator);

    return initializer;
  }

Flyway 目前仅支持阻塞 JDBC API,如果可能不将它们混合在同一个应用程序中,它与反应式 r2dbc 不兼容。

  1. 尝试注册一个 ConnectionFactoryInitializer 以启动数据库模式和数据,正如@Chris 发布的那样,my working example 可以在这里找到。

  2. 尝试 nkonev/r2dbc-migrate,它正在尝试将 flyway 迁移到 R2dbc 世界。

我在设置和访问内存中的 h2 数据库进行测试时遇到了同样的问题:

  • 使用 JDBC 驱动程序进行数据库迁移的 Liquibase
  • 使用 R2DBC 驱动程序测试 Reactive Crud 存储库

遇到错误:

org.springframework.data.r2dbc.BadSqlGrammarException: executeMany; bad SQL grammar [INSERT INTO MY_TABLE... Table "MY_TABLE" not found ...

受 Chris 解决方案的启发,我将 src/testresources/application.properties 文件配置如下:

spring.r2dbc.url=r2dbc:h2:mem:///~/db/testdb
spring.r2dbc.username=sa
spring.r2dbc.password=

spring.liquibase.url=jdbc:h2:mem:~/db/testdb;DB_CLOSE_DELAY=-1
spring.liquibase.user=sa
spring.liquibase.password=
spring.liquibase.enabled=true

我能够让它工作。

首先我创建了以下测试配置class(因为我只想再次对 H2 执行测试,在生产模式下我使用的是 PostgreSQL):

@TestConfiguration
public class TestConfig {
    @Bean
    @Profile("test")
    public ConnectionFactory connectionFactory() {
        System.out.println(">>>>>>>>>> Using H2 in mem R2DBC connection factory");
        return H2ConnectionFactory.inMemory("testdb");
    }

    @Bean(initMethod = "migrate")
    @Profile("test")
    public Flyway flyway() {
        System.out.println("####### Using H2 in mem Flyway connection");
        return new Flyway(Flyway.configure()
                .baselineOnMigrate(true)
                .dataSource(
                        "jdbc:h2:mem:testdb",
                        "sa",
                        "")
        );
    }
}

正如您在上面的代码中看到的,这两个 bean 的范围仅限于“测试”配置文件。正如您可以想象的那样,我在常规 ApplicationConfiguration class 中有几乎相同的 bean,但注释为 @Profile("default") 并配置为使用 PostgreSQL。

第二件事是我创建了结合其他几个注释的注释,以便不重复我自己并轻松拾取在 TestConfig class:

中声明的 beans
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootTest
@ActiveProfiles("test")
@Import(TestConfig.class)
public @interface IntegrationTest {
}

现在测试本身:

@IntegrationTest
class CartsIntegrationTest {
 // test methods here ....
}

我相信主要提示是使用 H2ConnectionFactory.inMemory("testdb");