H2内存数据库。表未找到。

249

我有一个使用URL为 "jdbc:h2:test" 的 H2数据库。 我使用 CREATE TABLE PERSON (ID INT PRIMARY KEY, FIRSTNAME VARCHAR(64), LASTNAME VARCHAR(64)); 创建了一个表。 然后,我使用 SELECT * FROM PERSON 从这个(空的)表中选择所有内容。 到目前为止一切都很好。

然而,如果我将URL更改为"jdbc:h2:mem:test",唯一的区别是数据库现在只存在于内存中,那么它会给出一个错误信息:org.h2.jdbc.JdbcSQLException: Table "PERSON" not found; SQL statement: SELECT * FROM PERSON [42102-154]。我可能错过了一些简单的东西,但任何帮助都将不胜感激。


2
切换到内存模式后,您需要重新创建表“Person”。H2不知道您之前在磁盘上创建的数据库。 - Benjamin Muschko
程序的其余部分没有改变 - 我只是重新创建了表格。 - Jorn
1
请参考:如何在连接之间保留H2内存数据库 - Basil Bourque
你如何连接?我怀疑你正在连接到不同的数据库,因为你没有指定任何东西(提示:只通过更改URL无法在服务器模式下创建)。内存数据库需要在服务器模式下创建才能允许多个连接;没有服务器模式,它是私有的,只能在同一JVM中使用,并且不能从其他进程连接。这就是为什么我认为你正在重新创建数据库,而你看不到应用程序数据库的数据。所以,要么你创建一个文件并使用服务器模式,要么你创建内存数据库但也在服务器模式下。并且你需要使用TCP进行连接。请参见我的答案。 - WesternGun
@WesternGun 感谢您的回答,但这个问题已经在10年前得到正确的回答了。 - Jorn
实际上我刚刚发现,如果我使用被接受的答案,下一次启动应用程序会看到“地址已经在使用中”,因为之前的会话占用了相同的端口,所以存在一些副作用,正确的答案可能不是真正的解决方案。 - WesternGun
27个回答

426

DB_CLOSE_DELAY=-1

在创建表之后,hbm2ddl 会关闭连接,因此 h2 会丢弃它。

如果你的 connection-url 配置如下:

jdbc:h2:mem:test

当最后一个连接关闭时,数据库的内容就会丢失。

如果想保留你的内容,你需要像这样配置URL。

jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
如果这样做,h2会在vm存活期间保持其内容。
请注意分号(;)而不是冒号(:)。
请参阅功能页面的内存数据库部分。引用如下:
“默认情况下,关闭与数据库的最后一个连接将关闭该数据库。对于内存数据库,这意味着内容将丢失。要保持数据库打开,请在数据库URL中添加;DB_CLOSE_DELAY=-1。要使内存数据库的内容与虚拟机存活期间一直存在,请使用jdbc:h2:mem:test;DB_CLOSE_DELAY=-1。”

我自己找到了问题,但是没错,这完全正确。谢谢! - Jorn
5
它必须是一个命名的内存数据库,即jdbc:h2:mem:;DB_CLOSE_DELAY=-1不起作用。 - Peter Becker
我们如何将数据存储在文件中而不是内存中? - Suleman khan
19
如果你在代码中使用小写命名表格,需要知道H2默认将其全部转换成大写。使用DATABASE_TO_UPPER=false可以避免这种情况发生,示例如下: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false; - Oleksandr Petrenko
@OleksandrPetrenko - 那个尾随的 ';' 似乎会引起问题。我认为你需要把它去掉。 - Volksman
显示剩余2条评论

157

我知道这不是你的情况,但我遇到了同样的问题,因为H2将表名创建为大写字母,然后对大小写敏感,即使在所有脚本(包括创建脚本)中我都使用小写字母。

通过在连接URL中添加 ;DATABASE_TO_UPPER=false 解决了该问题。


11
哇——我很高兴你分享了这个!我从未想过那个。 - ms-tg
1
这不是对所问问题的解决方案,而是当我用同样的问题搜索时遇到的问题的解决方案! - Yaytay
在初始化脚本中,是否可以将DATABASE_TO_UPPER=false设置为SQL语句?(类似于SET MODE PostgreSQL;这样的语句)如果可以,确切的语法是什么? - Jonik
7
我应该如何多次点赞这个?非常感谢!这应该是第一个答案的一部分。 - Ribesg

100

针对Spring Boot 2.4+版本,在application.properties文件中使用如下配置:spring.jpa.defer-datasource-initialization=true


8
太准确了 :) 解决了我的问题。谢谢! - Rui Carvalho
6
这确实是一个很好的解决方案。您能否解释一下这个语句的作用? - Suhail Akhtar
谢谢兄弟。 - Maninder
这是目前为止最好的解决方案,完美地运作。 - Bigyan Devkota
1
@AlGrant 它将数据源的初始化推迟到任何EntityManagerFactory bean被创建和初始化之后。(https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html) - Ismayil Karimli
显示剩余2条评论

12

很难说。我创建了一个程序来测试这个问题:

package com.gigaspaces.compass;

import org.testng.annotations.Test;

import java.sql.*;

public class H2Test {
@Test
public void testDatabaseNoMem() throws SQLException {
    testDatabase("jdbc:h2:test");
}
@Test
public void testDatabaseMem() throws SQLException {
    testDatabase("jdbc:h2:mem:test");
}

private void testDatabase(String url) throws SQLException {
    Connection connection= DriverManager.getConnection(url);
    Statement s=connection.createStatement();
    try {
    s.execute("DROP TABLE PERSON");
    } catch(SQLException sqle) {
        System.out.println("Table not found, not dropping");
    }
    s.execute("CREATE TABLE PERSON (ID INT PRIMARY KEY, FIRSTNAME VARCHAR(64), LASTNAME VARCHAR(64))");
    PreparedStatement ps=connection.prepareStatement("select * from PERSON");
    ResultSet r=ps.executeQuery();
    if(r.next()) {
        System.out.println("data?");
    }
    r.close();
    ps.close();
    s.close();
    connection.close();
}
}

测试已经完成,没有失败和意外的输出。你使用的h2版本是哪个?


明天我会尝试这个,谢谢。H2版本是我今天从网站上下载的:1.3.154。 - Jorn
1
我想我找到了问题所在。当我关闭与创建表的连接时,然后打开一个新连接时,数据库就会消失。当我在关闭上一个连接之前打开一个新连接时,数据仍然存在。当我使用文件时,数据(显然)总是保留。 - Jorn

12
打开 h2-console 时,JDBC URL 必须与属性中指定的 URL 匹配。
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:testdb

spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true

spring.h2.console.enabled=true

输入图像描述

这似乎很显然,但我花了几个小时才弄清楚...


2
感谢您在面试作业即将到期之前的帮助,我因为这个问题浪费了几个宝贵的小时,直到看到这条评论! - satish marathe

11

可能的原因之一是JPA尝试在创建表结构之前插入数据,为了解决这个问题,在application.properties文件中添加以下行:

spring.jpa.defer-datasource-initialization=true

8

内存中的H2数据库

H2数据库将数据存储在JVM内存中。当JVM退出时,这些数据也会丢失。

我猜想您正在做类似于下面两个Java类的事情。其中一个类创建表格,另一个类尝试插入数据:

import java.sql.*;

public class CreateTable {
    public static void main(String[] args) throws Exception {
        DriverManager.registerDriver(new org.h2.Driver());
        Connection c = DriverManager.getConnection("jdbc:h2:mem:test");
        PreparedStatement stmt = c.prepareStatement("CREATE TABLE PERSON (ID INT PRIMARY KEY, FIRSTNAME VARCHAR(64), LASTNAME VARCHAR(64))");
        stmt.execute();
        stmt.close();
        c.close();
    }
}

并且

import java.sql.*;

public class InsertIntoTable {
    public static void main(String[] args) throws Exception {
        DriverManager.registerDriver(new org.h2.Driver());
        Connection c = DriverManager.getConnection("jdbc:h2:mem:test");
        PreparedStatement stmt = c.prepareStatement("INSERT INTO PERSON (ID, FIRSTNAME, LASTNAME) VALUES (1, 'John', 'Doe')");
        stmt.execute();
        stmt.close();
        c.close();
    }
}

当我依次运行这些类时,我得到了以下输出:
C:\Users\Luke\stuff>java CreateTable

C:\Users\Luke\stuff>java InsertIntoTable
Exception in thread "main" org.h2.jdbc.JdbcSQLException: Table "PERSON" not found; SQL statement:
INSERT INTO PERSON (ID, FIRSTNAME, LASTNAME) VALUES (1, 'John', 'Doe') [42102-154]
        at org.h2.message.DbException.getJdbcSQLException(DbException.java:327)
        at org.h2.message.DbException.get(DbException.java:167)
        at org.h2.message.DbException.get(DbException.java:144)
        ...
第一个 java 进程退出后,由 CreateTable 创建的表就不存在了。因此,当 InsertIntoTable 类出现时,没有表可供其插入。
当我将连接字符串更改为 jdbc:h2:test 时,发现没有此错误。我还发现一个名为 test.h2.db 的文件已经出现。这是 H2 放置表的位置,因为它被存储在磁盘上,所以表仍然存在于 InsertIntoTable 类中找到。

1
请注意,registerDriver() 调用是不必要的:首先,对于大多数 JDBC 驱动程序,一个简单的 Class.forName() 就可以完成同样的工作 而且(更重要的是)对于 Java 6 及以上版本来说,它完全是不必要的,因为它会自动检测类路径上的(兼容的)JDBC 驱动程序。 - Joachim Sauer
一个内存数据库只存在于拥有该内存的程序运行期间?哇,我真不知道 > _ < 但是,实际上,我知道自己在尝试做什么。看了你的回答,我不确定你是否理解。 - Jorn
2
@Jorn:我可能不知道你想做什么,只是根据你提供的信息猜测。如果提供一个SSCCE(http://sscce.org/)演示你的问题可能更有帮助——在这方面上,我不会认为你的问题是“完整的”。我提供上述答案是因为在SO上有些人(主要是编程新手)可能会认为“内存中”数据库将数据存储在计算机的某个内存中,在程序调用之间可以保存数据。你的问题不够完整,无法让我相信你不是其中的一员。 - Luke Woodward

7

我已经尝试添加

jdbc:h2:mem:test;DB_CLOSE_DELAY=-1

然而,这并没有解决问题。在H2网站上,我找到了以下信息,确实可以在某些情况下提供帮助。

默认情况下,关闭与数据库的最后一个连接也会关闭数据库。对于内存中的数据库,这意味着内容将丢失。要保持数据库开放,需要在数据库URL中添加;DB_CLOSE_DELAY=-1。为了使内存中的数据库内容保持在虚拟机存活期间,请使用jdbc:h2:mem:test;DB_CLOSE_DELAY=-1。

然而,我的问题是模式应该与默认模式不同。因此,不是使用
JDBC URL: jdbc:h2:mem:test

我必须使用:

JDBC URL: jdbc:h2:mem:testdb

然后表格变得可见。

谢谢,"testdb" 对我也有帮助(除了 "DB_CLOSE_DELAY=-1")! - boly38

7
通过创建一个新的src/test/resources文件夹并插入application.properties文件,显式地指定创建测试数据库来解决问题。
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create

6
我试图获取表格元数据,但出现了以下错误:
使用:
String JDBC_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1";

DatabaseMetaData metaData = connection.getMetaData();
...
metaData.getColumns(...);

“返回了一个空的结果集。” “但是使用下面的URL,它正常工作:”
String JDBC_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false";

有必要明确指定:DATABASE_TO_UPPER=false

2
这并没有增加任何未被此答案涵盖的内容。来自审核 - Wai Ha Lee

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