読者です 読者をやめる 読者になる 読者になる

H2をインメモリで動かすときの注意

H2をインメモリで動かすときは

jdbc:h2:mem:test

みたいな感じでmemを付けて開いてあげます。

ただこのまま起動すると、ちょっと注意が必要でconnectionをcloseするとその時点でDBが消滅します。これ知らないと最初のcreate tableは成功しているのに、あとからinsertしようとするとテーブルないとか言われるよ!みたいなことではまります(お、お、おれじゃないからな!)。

消えないようにするにはconnectionをひとつ開いてどっかでcloseせずに保持しておくか、オプションでDB_CLOSE_DELAYを付けてあげるかどちらかです。

詳しくはテストを。

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.h2.Driver;
import org.junit.Test;

public class H2MemDbTest {

    @Test(expected = SQLException.class)
    public void コネクションをcloseするとDBが消える() throws Exception {
        Driver.load();
        Connection con1 = DriverManager.getConnection("jdbc:h2:mem:test");
        Statement st1 = con1.createStatement();
        st1.execute("create table test (id int primary key,name varchar)");
        st1.execute("insert into test values (1, 'hoge')");
        ResultSet rs1 = st1.executeQuery("select * from test");
        assertThat(rs1.next(), is(true));
        st1.close();
        con1.close();

        Connection con2 = null;
        Statement st2 = null;
        try {
            con2 = DriverManager.getConnection("jdbc:h2:mem:test");
            st2 = con2.createStatement();

            // 一度コネクションクローズしているのでテーブルが見つからず
            // SQLExceptionがthrowされる
            st2.executeQuery("select * from test");
        } finally {
            st2.close();
            con2.close();
        }
    }

    @Test
    public void コネクションをひとつ開きっぱなしにすると問題ない() throws Exception {
        Driver.load();
        Connection con1 = DriverManager.getConnection("jdbc:h2:mem:test");
        Statement st1 = con1.createStatement();
        st1.execute("create table test (id int primary key,name varchar)");
        st1.execute("insert into test values (1, 'hoge')");
        ResultSet rs1 = st1.executeQuery("select * from test");
        assertThat(rs1.next(), is(true));
        st1.close();
        // con1はクローズしない

        Connection con2 = DriverManager.getConnection("jdbc:h2:mem:test");
        Statement st2 = con2.createStatement();
        ResultSet rs2 = st2.executeQuery("select * from test");
        assertThat(rs2.next(), is(true));

        st2.close();
        con2.close();
        con1.close();
    }

    @Test
    public void DB_CLOSE_DELAYオプション付きで最初に開く() throws Exception {
        Driver.load();
        Connection con1 = DriverManager.getConnection("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"); // DB_CLOSE_DELAYオプション付きで開く
        Statement st1 = con1.createStatement();
        st1.execute("create table test (id int primary key,name varchar)");
        st1.execute("insert into test values (1, 'hoge')");
        ResultSet rs1 = st1.executeQuery("select * from test");
        assertThat(rs1.next(), is(true));
        st1.close();
        con1.close();

        Connection con2 = DriverManager.getConnection("jdbc:h2:mem:test");
        Statement st2 = con2.createStatement();
        ResultSet rs2 = st2.executeQuery("select * from test");
        assertThat(rs2.next(), is(true));
        st2.close();
        con2.close();
    }
}

ちなみにH2の中のDbStarter(Webアプリ起動時にH2立ち上げてくれるかわいい娘)は

localServletContext.setAttribute("connection", this.conn);

とかやってServletContextにconnectionを1つ保持するようになっているようですね。


最近もっぱらDB周りのユニットをテスト書くときは、H2をテストケースの前に起動してみたいなことをやります。爆速で感謝感謝であります。