使用内存数据库时的R2dbc H2问题

6

我尝试使用嵌入式 H2 数据库来体验 R2dbc,方法如下:

public ConnectionFactory connectionFactory() {
        //ConnectionFactory factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
        return new H2ConnectionFactory(
                H2ConnectionConfiguration.builder()
                        //.inMemory("testdb")
                        .file("./testdb")
                        .username("user")
                        .password("password").build()
        );
    }

我定义了一个Bean来创建表格和初始化数据。

@Bean
    public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {

        ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
        initializer.setConnectionFactory(connectionFactory);

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

        return initializer;
    }

我还定义了另一个组件来通过Java代码设置数据。


@Component
@Slf4j
class DataInitializer {

    private final DatabaseClient databaseClient;

    public DataInitializer(DatabaseClient databaseClient) {
        this.databaseClient = databaseClient;
    }

    @EventListener(value = ContextRefreshedEvent.class)
    public void init() {
        log.info("start data initialization  ...");
        this.databaseClient.insert()
            .into("posts")
            //.nullValue("id", Integer.class)
            .value("title", "First post title")
            .value("content", "Content of my first post")
            .map((r, m) -> r.get("id", Integer.class))
            .all()
            .log()
            .thenMany(
                this.databaseClient.select()
                    .from("posts")
                    .orderBy(Sort.by(desc("id")))
                    .as(Post.class)
                    .fetch()
                    .all()
                    .log()
            )
            .subscribe(null, null, () -> log.info("initialization done..."));
    }

}

如果我在ConnectionFactory bean定义中使用了.inMemory("testdb"),当Spring ApplicationContext初始化时,会因为在初始化DataInitializer找不到表格POSTS而失败。从启动日志中可以看到,ConnectionFactoryInitializer已经成功初始化,并执行了schema.sql和data.sql以创建表格POSTS并插入数据。

但是,如果改用.file("./testdb"),则可以正常工作。

完整的代码在这里

2个回答

7

我从Spring Data R2dc的开发者@mp911de那里得到了答案。请参见#issue269

该问题与H2在最后一个连接关闭时关闭数据库的行为有关。请配置DB_CLOSE_DELAY=-1选项,以便H2保留数据库。或者,使用H2ConnextionFactory.inMemory(...)工厂方法创建一个Closeable连接工厂,它不依赖于正在使用的连接计数。

将我的代码更改为以下内容,它可以正常工作:


 public ConnectionFactory connectionFactory() {       
     return H2ConnectionFactory.inMemory("testdb");
 }

4

来自官方文档

ConnectionFactory

(注:该页面需要在翻墙状态下查看)
@Configuration
public class ApplicationConfiguration extends AbstractR2dbcConfiguration {

  @Override
  @Bean
  public ConnectionFactory connectionFactory() {
    return …;
  }
}

这种方法允许您使用标准的io.r2dbc.spi.ConnectionFactory实例,并使用Spring的AbstractR2dbcConfiguration容器。相比直接注册ConnectionFactory实例,配置支持还具有额外的优势,即为带有@Repository注释的数据访问类提供了一种将R2DBC异常转换为Spring可移植DataAccessException层次结构中的异常的ExceptionTranslator实现。
此外,AbstractR2dbcConfiguration还注册了DatabaseClient,这对于数据库交互和Repository实现是必需的。
我写了一篇关于如何使用R2DBC入门的博客文章,可以在这里找到. 这里是一个使用H2数据库的示例。
我猜测您没有正确初始化连接工厂,因此没有得到一个合适的DatabaseClient。

1
我不这么认为。从启动日志来看,ConnectionFactory 是正常的,ConnectionFactoryInitializer 成功初始化(创建并插入数据),但是当初始化 DataInitializer 以插入另一个数据时,由于找不到“posts”表而失败。我提到使用 .file("./testdb") 而不是 InMemory,它可以工作。 - Hantsy
显然不是这样,否则你就不会在这里写了。 - Toerktumlare
@ThomasAndolf,在您的文章中提到了使用postgresql的application.properties,但是:我们如何在H2DB中使用application.properties? - GtdDev
1
如果您不设置属性,应用程序将默认使用H2DB。 - Toerktumlare

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接