使用 inMemory 数据库时出现 R2dbc H2 问题

Posted

技术标签:

【中文标题】使用 inMemory 数据库时出现 R2dbc H2 问题【英文标题】:R2dbc H2 issues when using inMemory database 【发布时间】:2020-04-23 02:25:36 【问题描述】:

我尝试体验 R2dbc 并使用 Embedded H2,例如:

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 初始化成功,创建表 POSTS 并通过执行 schema.sql 和 data.sql 按预期插入数据。

但是切换到使用.file("./testdb"),它起作用了。

完整代码为here。

【问题讨论】:

【参考方案1】:

我从 Spring Data R2dc 开发人员@mp911de 那里得到了答案。见#issue269

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

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


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

【讨论】:

【参考方案2】:

来自官方文档

ConnectionFactory

@Configuration
public class ApplicationConfiguration extends AbstractR2dbcConfiguration 

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

这种方法让您可以使用标准的 io.r2dbc.spi.ConnectionFactory 例如,容器使用 Spring 的 AbstractR2dbc 配置。相比注册一个 ConnectionFactory 实例直接,配置支持有 还为容器提供了一个额外的优势 将 R2DBC 异常转换为的 ExceptionTranslator 实现 Spring 的可移植 DataAccessException 数据层次结构中的异常 访问使用@Repository 注解注解的类。

(...)

AbstractR2dbcConfiguration 还注册了 DatabaseClient,这是数据库交互和 Repository 实现所必需的。

我写了一篇关于如何设置 R2DBC 入门的博客文章here。 Here是一个使用H2数据库的例子

我的猜测是您没有正确初始化连接工厂,因此没有获得正确的 DatabaseClient。

【讨论】:

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

以上是关于使用 inMemory 数据库时出现 R2dbc H2 问题的主要内容,如果未能解决你的问题,请参考以下文章

R2DBC 无法使用 h2 协议创建连接

从 H2 数据库中读取 TINYINT 值作为布尔值/布尔值时出现 MappingException

何时使用 apollo-link-state 以及何时使用 apollo-cache-inmemory

带有 JPA 和 R2DBC 的 Spring Boot 2.4 混合项目无法启动

spring security oauth2 (2.0.8) 使用 InMemory tokenstore 获取无效访问令牌

使用 R2DBC 进行数据库迁移