245

I've got a H2 database with URL "jdbc:h2:test". I create a table using CREATE TABLE PERSON (ID INT PRIMARY KEY, FIRSTNAME VARCHAR(64), LASTNAME VARCHAR(64));. I then select everything from this (empty) table using SELECT * FROM PERSON. So far, so good.

However, if I change the URL to "jdbc:h2:mem:test", the only difference being the database is now in memory only, this gives me an org.h2.jdbc.JdbcSQLException: Table "PERSON" not found; SQL statement: SELECT * FROM PERSON [42102-154]. I'm probably missing something simple here, but any help would be appreciated.

Synesso
  • 37,610
  • 35
  • 136
  • 207
Jorn
  • 20,612
  • 18
  • 79
  • 126
  • 2
    After switching to in-memory mode you got to create the table `Person` again. H2 doesn't know anything about the database you created on disk before. – Benjamin Muschko Apr 23 '11 at 12:03
  • The rest of the program didn't change - I did create the table again. – Jorn Apr 23 '11 at 22:35
  • 1
    See: [*Keep H2 in-memory database between connections*](https://dba.stackexchange.com/q/224338/19079) – Basil Bourque Dec 06 '18 at 21:15
  • How do you connect? I suspect you are connecting to different database, as you don't specify anything(hint: it cannot be created in server mode just by changing URL). In-memory database needs to be created in server mode to allow more than 1 connection; without server mode, it's private to the same JVM and cannot be connected from other processes. That's why I think you are recreating the db and you don't see the data of application db. So, or you create a file and use server mode, or you create in-memory db but also in server mode. And you need to connect with TCP. See my answer. – WesternGun Feb 19 '23 at 16:46
  • @WesternGun Thanks for the input but this question has already been answered correctly over 10 years ago. – Jorn Feb 19 '23 at 20:56
  • Actually just found that if I use the accepted answer, next launch of app will see "Address already in use" because the previous session occupies the same port, so there's some side effect and the correct answer may not be the real solution here. – WesternGun Feb 20 '23 at 07:37

27 Answers27

422

DB_CLOSE_DELAY=-1

hbm2ddl closes the connection after creating the table, so h2 discards it.

If you have your connection-url configured like this

jdbc:h2:mem:test

the content of the database is lost at the moment the last connection is closed.

If you want to keep your content you have to configure the url like this

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

If doing so, h2 will keep its content as long as the vm lives.

Notice the semicolon (;) rather than colon (:).

See the In-Memory Databases section of the Features page. To quote:

By default, closing the last connection to a database closes the database. For an in-memory database, this means the content is lost. To keep the database open, add ;DB_CLOSE_DELAY=-1 to the database URL. To keep the content of an in-memory database as long as the virtual machine is alive, use jdbc:h2:mem:test;DB_CLOSE_DELAY=-1.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
reini2901
  • 4,236
  • 1
  • 14
  • 5
157

I know this was not your case but I had the same problem because H2 was creating the tables with UPPERCASE names then behaving case-sensitive, even though in all scripts (including in the creation ones) i used lowercase.

Solved by adding ;DATABASE_TO_UPPER=false to the connection URL.

Cristian Vrabie
  • 3,972
  • 5
  • 30
  • 49
  • 11
    Wow - I'm very glad you shared this one! never would have thought of that. – ms-tg Nov 13 '13 at 21:30
  • 1
    Not the solution to the question that was asked, but the solution to the problem I was having when searching with the same question! – Yaytay Jan 22 '15 at 12:12
  • Is it possible to set this `DATABASE_TO_UPPER=false` thing as an SQL statement in an init script? (Similarly as a statement like `SET MODE PostgreSQL;`) If so, what is the exact syntax? – Jonik Nov 04 '15 at 10:41
  • 7
    How can I upvote this multiple times? Thanks a lot! This should be part of the first answer. – Ribesg Apr 08 '17 at 20:07
96

For Spring Boot 2.4+ use spring.jpa.defer-datasource-initialization=true in application.properties

Shounak Bose
  • 990
  • 6
  • 5
12

Hard to tell. I created a program to test this:

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();
}
}

The test ran to completion, with no failures and no unexpected output. Which version of h2 are you running?

Joseph Ottinger
  • 4,911
  • 1
  • 22
  • 23
  • I'll try this tomorrow, thanks. The H2 version is the one I got off the site today: 1.3.154 – Jorn Apr 23 '11 at 22:41
  • 1
    I think I found the problem. When I close the connection the table was created with, then open a new one, the db is gone. When I open a new connection before I close the previous one, the data remains. When I use a file, the data (obviously) always remains. – Jorn Apr 24 '11 at 12:10
12

When opening the h2-console, the JDBC URL must match the one specified in the properties:

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

enter image description here

Which seems obvious, but I spent hours figuring this out..

nagy.zsolt.hun
  • 6,292
  • 12
  • 56
  • 95
  • 2
    thanks on an interview assignment due in next few hours and lost a couple of valuable hours till I saw this comment , which was the issue ! – satish marathe Mar 30 '21 at 08:58
10

One reason can be that jpa tries to insert data before creating table structure, in order to solve this problem , insert this line in application.properties :

spring.jpa.defer-datasource-initialization=true
khalid tounoussi
  • 499
  • 5
  • 21
8

The H2 in-memory database stores data in memory inside the JVM. When the JVM exits, this data is lost.

I suspect that what you are doing is similar to the two Java classes below. One of these classes creates a table and the other tries to insert into it:

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();
    }
}

and

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();
    }
}

When I ran these classes one after the other, I got the following output:

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)
        ...

As soon as the first java process exits, the table created by CreateTable no longer exists. So, when the InsertIntoTable class comes along, there's no table for it to insert into.

When I changed the connection strings to jdbc:h2:test, I found that there was no such error. I also found that a file test.h2.db had appeared. This was where H2 had put the table, and since it had been stored on disk, the table was still there for the InsertIntoTable class to find.

Luke Woodward
  • 63,336
  • 16
  • 89
  • 104
  • 1
    Please note that the `registerDriver()` call is unnecessary: First of: a simple Class.forName() does the same for most JDBC drivers **and** (more importantly) it's completely unnecessary for Java 6 und up, which auto-detects (compatible) JDBC drivers on the classpath. – Joachim Sauer Apr 23 '11 at 13:06
  • An in memory db exists only as long as the program that owns the memory is running? Wow, I had no idea >_< But really, I know what I'm trying to do. Reading your answer, I'm not sure you do. – Jorn Apr 23 '11 at 22:39
  • 2
    @Jorn: I might not know what you're trying to do, I'm guessing based on what information you provided. It may have been more helpful to provide an SSCCE (http://sscce.org/) demonstrating your problem - I wouldn't call your question 'complete' in that respect. I provided the above answer because there are people on SO (newcomers to programming, mainly) that might think that an 'in-memory' database stores the data in the computer's memory somewhere where it could survive between program invocations. Your question wasn't complete enough to convince me you weren't one of these people. – Luke Woodward Apr 24 '11 at 07:43
7

I have tried to add

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

However, that didn't helped. On the H2 site, I have found following, which indeed could help in some cases.

By default, closing the last connection to a database closes the database. For an in-memory database, this means the content is lost. To keep the database open, add ;DB_CLOSE_DELAY=-1 to the database URL. To keep the content of an in-memory database as long as the virtual machine is alive, use jdbc:h2:mem:test;DB_CLOSE_DELAY=-1.

However, my issue was that just the schema supposed to be different than default one. So insted of using

JDBC URL: jdbc:h2:mem:test

I had to use:

JDBC URL: jdbc:h2:mem:testdb

Then the tables were visible

DevDio
  • 1,525
  • 1
  • 18
  • 26
7

Solved by creating a new src/test/resources folder + insert application.properties file, explicitly specifying to create a test dbase :

spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create
N.MATHIEU
  • 65
  • 1
  • 1
6

I had the same problem and changed my configuration in application-test.properties to this:

#Test Properties
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop

And my dependencies:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.198</version>
        <scope>test</scope>
    </dependency>

And the annotations used on test class:

@RunWith(SpringRunner.class)
@DataJpaTest
@ActiveProfiles("test")
public class CommentServicesIntegrationTests {
...
}
Georgi Peev
  • 912
  • 1
  • 14
  • 25
6

I was trying to fetch table meta data, but had the following error:

Using:

String JDBC_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1";

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

returned an empty ResultSet.

But using the following URL instead it worked properly:

String JDBC_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false";

There was a need to specify: DATABASE_TO_UPPER=false

  • 2
    This doesn't add anything not covered by [this](https://stackoverflow.com/a/17925668/1364007) answer. [From Review](https://stackoverflow.com/review/low-quality-posts/22862428). – Wai Ha Lee Apr 27 '19 at 02:02
4

I have tried adding ;DATABASE_TO_UPPER=false parameter, which it did work in a single test, but what did the trick for me was ;CASE_INSENSITIVE_IDENTIFIERS=TRUE.

At the end I had: jdbc:h2:mem:testdb;CASE_INSENSITIVE_IDENTIFIERS=TRUE

Moreover, the problem for me was when I upgraded to Spring Boot 2.4.1.

stergipe
  • 125
  • 1
  • 1
  • 11
  • 1
    I upgraded Spring Boot from 1.3.3.RELEASE to 2.4.4 and adding `;CASE_INSENSITIVE_IDENTIFIERS=TRUE` also did the trick for me. – Armando Hoyos May 15 '21 at 02:57
3

I might be a little late to the party, but I faced exactly the same error and I tried pretty much every solution mentioned here and on other websites such as *DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1; DB_CLOSE_ON_EXIT=FALSE; IGNORECASE=TRUE*. But nothing worked for me.

What worked for me was renaming data.sql to import.sql

I found it here - https://stackoverflow.com/a/53179547/8219358

Or

For Spring Boot 2.4+ use spring.jpa.defer-datasource-initialization=true in application.properties (mentioned here - https://stackoverflow.com/a/68086707/8219358)

I realize other solutions are more logical but none of them worked for me and this did.

HARSHIT BAJPAI
  • 361
  • 2
  • 17
  • The rename to import.sql is what worked for me. I feel like the "defer=true" is a workaround...while the rename is the "actually fix it for Spring 2.5+". Thank you. – granadaCoder Jul 28 '22 at 11:46
3

Had similar problem Solution was to add the following to application.properties

spring.jpa.defer-datasource-initialization=true

magdi amer
  • 66
  • 4
2

I came to this post because I had the same error.

In my case the database evolutions weren't been executed, so the table wasn't there at all.

My problem was that the folder structure for the evolution scripts was wrong.

from: https://www.playframework.com/documentation/2.0/Evolutions

Play tracks your database evolutions using several evolutions script. These scripts are written in plain old SQL and should be located in the conf/evolutions/{database name} directory of your application. If the evolutions apply to your default database, this path is conf/evolutions/default.

I had a folder called conf/evolutions.default created by eclipse. The issue disappeared after I corrected the folder structure to conf/evolutions/default

Oscar Fraxedas
  • 4,467
  • 3
  • 27
  • 32
2

Had the exact same issue, tried all the above, but without success. The rather funny cause of the error was that the JVM started too fast, before the DB table was created (using a data.sql file in src.main.resources). So I've put a Thread.sleep(1000) timer to wait for just a second before calling "select * from person". Working flawlessly now.

application.properties:

spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

data.sql:

create table person
(
id integer not null,
name varchar(255) not null,
location varchar(255),
birth_date timestamp,
primary key(id)
);

insert into person values (
10001, 'Tofu', 'home', sysdate()
);

PersonJdbcDAO.java:

    public List<Person> findAllPersons(){
    return jdbcTemplate.query("select * from person", 
        new BeanPropertyRowMapper<Person>(Person.class));
}

main class:

Thread.sleep(1000);
logger.info("All users -> {}", dao.findAllPersons());
Tudor Gafiuc
  • 145
  • 1
  • 2
  • 8
2

I found it working after adding the dependency of Spring Data JPA -

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>

Add H2 DB configuration in application.yml -

spring:
  datasource:
    driverClassName: org.h2.Driver
    initialization-mode: always
    username: sa
    password: ''
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
  h2:
    console:
      enabled: true
      path: /h2
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: none
Bagesh Sharma
  • 783
  • 2
  • 9
  • 23
  • Question has nothing to do with spring, and solving simple problems bu throwing in frameworks is crude – Ben Jan 01 '21 at 18:39
2

I have tried the above solution,but in my case as suggested in the console added the property DB_CLOSE_ON_EXIT=FALSE, it fixed the issue.

 spring.datasource.url=jdbc:h2:mem:testdb;DATABASE_TO_UPPER=false;DB_CLOSE_ON_EXIT=FALSE
mathan
  • 455
  • 3
  • 10
1
<bean id="benchmarkDataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="org.h2.Driver" />
    <property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1" />
    <property name="username" value="sa" />
    <property name="password" value="" />
</bean>
Alex R
  • 11,364
  • 15
  • 100
  • 180
1

The issue can also happen if there was error while generating the table.

If the entities use any features which are not supported by H2 (for example, specifying a non-standard columnDefinition), the schema generation will fail and test will continue without the database generated.

In this case, somewhere in the logs you will find this:

WARN ExceptionHandlerLoggedImpl: GenerationTarget encountered exception accepting command :
 Error executing DDL "create table ..." via JDBC Statement
Alexandru Severin
  • 6,021
  • 11
  • 48
  • 71
0

In my case missing table error was happening during jpa test, table was created by schem.sql file, problem was fixed after puting @org.springframework.transaction.annotation.Transactional on test

Guram Kankava
  • 51
  • 1
  • 4
0

In my case, I had used the special keywords for my column-names in the table, H2 Database. If you're using different databases avoid those special keywords across different databases. Spring & Hibernate isn't smart enough to tell you exactly which column names are prohibited or where the exact error is in the table-creation. Keywords such as;

desc, interval, metric

To resolve the issues I was experiencing, I renamed those fields to:

descr, time_interval, time_metric

http://www.h2database.com/html/advanced.html

S34N
  • 7,469
  • 6
  • 34
  • 43
0
   Use the same in applications.properties file
   
   spring.jpa.show-sql=true
   spring.datasource.url=jdbc:h2:mem:testdb;DATABASE_TO_UPPER=false
   DB_CLOSE_ON_EXIT=FALSE
   spring.data.jpa.repositories.bootstrap-mode=default
   spring.h2.console.enabled=true spring.jpa.generate-ddl=true
   spring.jpa.hibernate.ddl-auto=create
   spring.datasource.driverClassName=org.h2.Driver
   spring.jpa.defer-datasource-initialization=true
Waheed Khan
  • 104
  • 2
  • 10
0
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "user" not found; SQL statement:

in my case, my table name was user but from H2 2.1.212 user is reserved so couldn't make the table

changed table name users by @Table(name="users") and

datasource:
  url: jdbc:h2:mem:testdb;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1;

and it works now

Shane Park
  • 81
  • 4
0

This might be a beginners mistake but the error in my case was that, when I wanted to switch my DB to H2, I omitted a property that needed changing.

Namely: spring.jpa.properties.hibernate.dialect: org.hibernate.dialect.H2Dialect

Other usual suspects could be:

  • spring.datasource.driver-class-name: org.h2.Driver
  • spring.datasource.url: jdbc:h2:file:./your/path
  • spring.jpa.hibernate.ddl-auto: where you might have set a wrong value

I'm not sure whether those would result in different error messages. However, they are still worth a quick check.

Benjamin Basmaci
  • 2,247
  • 2
  • 25
  • 46
0

If you are using @DataJpaTest you need to bear in mind that by default it ignores the datasource defined in your properties and creates a new one of its own.

Due to this, most of the solutions will not work, specially if you are trying to use an in-memory database in combination with Flyway/Liquibase. For instance, DB_CLOSE_DELAY=-1 connection property is ignored and setting spring.jpa.defer-datasource-initialization=true causes a circular dependency with Flyway.

Allowing Hibernate to create the schema with ddl-auto=create is not ideal either if you want to ensure your migration scripts actually work, so the best solution is to use the AutoConfigureTestDatabase annotation to tell @DataJpaTest not to create its own database and to use the one defined in your properties files.

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class YourDatabaseIntegrationTest {
    ...
}
ibai
  • 1,143
  • 1
  • 17
  • 28
-1

I suspect the database you opened is a brand new db, not your application db. This is because:

  • H2 in-memory database by default is private to the JVM and the classloader. It cannot be connected from another process/with TCP from same machine/from another machine; that's why all the guides suggest using H2 console, because that's within the same JVM of your application so it can access the database(however, my H2 console shipped with Spring 2.6 is not working, I need to find another way)
  • H2 database can be launched in server mode, but ";AUTO_SERVER=true" does not work with in-memory db; it only can be added to the URL when you use a file based db; and to visit it you need to use absolute path to the db file, which is not portable and is ugly; additionally, auto-generation of tables are not done when you use a file so you need to create an init.sql to create tables. AND, when you connect H2 still tells you that you need server mode, because there is already one connection to the db(your app) and to allow 1+ connection, you need server mode

So in both cases you need server mode. How?

In Spring you need to create the DB as a bean(thanks to How to enable H2 Database Server Mode in Spring Boot); put this into a @Configuration and you are done:

@Bean(initMethod = "start", destroyMethod = "stop")
public Server h2Server() throws SQLException {
    return Server.createTcpServer("-tcp", "-tcpAllowOthers", "-tcpPort", "1234"); // or any other port
}

Your db url:

spring:
  datasource:
    url: jdbc:h2:mem:test
    driver-class-name: org.h2.Driver
    port: 1234
    username: sa
    password: sa

That's all. You can connect with H2 console, or DB Navigator, or other tools along with your app right now. The connection string is:

jdbc:h2:tcp://localhost:1234/mem:test
WesternGun
  • 11,303
  • 6
  • 88
  • 157