忍者ブログ

軽Lab

 Javaを中心とした、プログラミング関係のナレッジベース

Home > Java > 埋込RDBの利用(Derby,HSQLDB,H2ライブラリの性能比較)

埋込RDBの利用(Derby,HSQLDB,H2ライブラリの性能比較)

スタンドアロンなアプリケーションを作成する場合でも、大量のデータを扱う場合やエンタープライズ目的となるとデータの管理方法が煩雑になってくる。そんな場合にはRDBライブラリを利用するのが手っ取り早い。

純粋なJava実装のプログラム埋め込み型RDBとしては、Derby, H2, HSQLDBが有名である。今回は埋め込みDBとして利用する場合の、各DBライブラリ利用方法と性能を確認する。


■ Derby, HSQLDB, H2について

 3つともembeded(スタンドアロン)/server(ネットワーク)の両動作モードを持っている。embededモードは1つのプログラム内だけで利用(プログラム内に埋め込んで利用)する方法で、利用時にはjarファイルをクラスパスに通すだけで動作する(クラスパスの通し方は別記事参照)。serverモードはRDBが単独で動作し、ポート経由などで他PC・プログラムからの利用が可能なモードである。一般的にRDBといえばこの動作である。

図:embededモードとserverモードの挙動イメージ

 主な特徴をまとめると以下のとおりである。純粋なJavaプログラムとして作成されているため、Javaが入っていればどこでも動く(*2,*3,*5)。また、ライセンスも商用可能なものである点で一致している。
項目 Derby HSQLDB H2
起動モード embedded mode
server mode
embedded mode
server mode
Embedded mode
Server mode
Mixed mode
ライセンス Apache Software License 2.0 拡張BSD MPL 2.0、EPL 1.0から選択
ライブラリファイル derby.jar
JDK付属(jdkディレクトリ/db/lib配下)
hsqldb.jar
(公式よりダウンロード)
h2-(バージョン).jar
(公式よりダウンロード)
主な接続方法 JDBC JDBC JDBC
ODBC
接続文字列 jdbc:derby:○○;create=true
*「○○」にはDB用のフォルダパスが入る
jdbc:hsqldb:file:○○;shutdown=true
*「○○」にはDB用のファイルパスが入る
jdbc:h2:file:○○
*「○○」にはDB用のファイルパスが入る
主な付属ソフト ij : CIコンソール sqltool : CIコンソール H2 コンソール(GUI)

 次にそれぞれのDBについて、利用方法を確認する

■ Derby

 1997年にJBMSといして発表された後Cloudscapeに改名、IBMによる買収を経て、Apacheソフトウェア財団に寄贈されたという歴史を持つ。JDKではJavaDBという名前でライブラリが同梱されているが、JavaDBとApache Derbyと全く同じものである。今回紹介する3つの純正JavaRDBの中で最も古い。現在(2016年4月)の最新バージョンは2015年11月公開の10.12.1.1。

 筆者が利用時に引っかかった点は2つ。1つ目は接続文字列に指定するフォルダパスだが、空のフォルダパスを指定した場合エラーとなってしまう。必ず、存在しないフォルダ名か、以前にDerby DBフォルダとして作成されたパスを指定すること。2つ目は、DBViewer利用時に以下のメッセージが出た場合はeclipseの再起動が必要となる点である。筆者の環境では、Derby DBを切断し再接続する毎、常にこのメッセージが表示される。DBViewerではうまく切断ができないものと思われる。

再接続時に表示されるエラーメッセージ
クラス・ローダーzigen.plugin.db.core.PluginClassLoader@50ce3dc6を使用してデータベース’○○’を起動できません。詳細は次の例外を参照してください。

 これ以外にも、何か問題が起こった際にはコンソール(ij)を利用するとよい。コンソールであれば、完全なエラーメッセージを見ることができる。コンソールもJDKに同封されており、場所は「(jdkフォルダ)/db/bin」にである。

■ HSQLDB

 オープンソースRDBの中でも幅広い標準SQLに対応していると公言しており、SQL2011も対応済み。開発から15年経った今でも多くのソフトウェアで利用されており、OpenOfficeをはじめとする1700以上のオープンソースに採用されている。現在(2016年4月)の最新バージョンは2016年3月公開の2.3.4。

■ H2

 HSQLDBの生みの親、トーマス・ミュラー(Thomas mueller)によって開発されたRDB。コンパクトで高速な動作が目標となっている模様。現在(2016年4月)の最新バージョンは2016年1月公開の1.4.191。筆者が利用時に引っかかった点としては、接続文字列のDBファイルパスを相対パス指定する場合は「./」で始める必要がある点である。

■ 環境構築(DBViewerプラグインのインストール)

 eclipse上からデータベースを作成、管理するためのプラグインDBViewerの導入方法を確認する。ちなみに、各DB付属のコンソールプログラムや他のデータベース接続ソフトウェアを持っている場合には、このプラグインを入れる必要はない。DBViewerプラグインは直接DBを覗くための手段であり、プログラム上からRDBアクセスをするために必要なソフトではない。

■ DBViewerプラグインのインストール

1.メニュー「ヘルプ - 新規ソフトウェアのインストール」でインストール画面を開く。開いた画面の右上「追加」ボタンを押下


2.以下のように入力して「OK」ボタンを押下。
 名前    :DBViewer
 ロケーション:http://www.ne.jp/asahi/zigen/home/plugin/dbviewer/


3.「DBViewer Plugins」を選択し、「次へ」ボタンを押下。ここから先は指示に従って進んでいけばインストールできる。


■ DBViewerプラグインの利用

1.インストールが完了するとeclipse画面の右上、「パースペクティブを開く」のメニューに「DBViewer」が追加されているので選択。
 

2.RDBに接続する場合はDBViewerパースペクティブの画面左「DBViewerPlugin」を左クリック。ポップアップメニューの「登録」を選択する。


3.表示されるウィンドウで、「データベース定義名(=DBのID)」と「JDBCドライバー」を入力する。JDBCドライバー入力は、「ファイルの追加」押下して利用するDBのライブラリファイルを指定する。以下はderbyを利用する際のイメージ。入力後「次へ」ボタンを押下。


4.適切な接続文字列を入力し、「接続ユーザ」「接続パスワード」に初期ユーザ情報を登録して「テスト接続」ボタンを押下。接続に成功すると画面上部にメッセージが表示され、接続文字列で指定した場所にDBファイルが作成される。「完了」ボタンを押下する。


5.DBツリー・ビューに作成したDBが登録されているので、右クリックから「接続」を選択するとDBに接続される。


6.SQL実行・ビューに実行したいSQLを入力し実行ボタンを押下すると、SQLが発行される。


■ サンプル・プログラム

 以下にRDB(Derby,H2,HSQLDB)に接続してSQLを発行するサンプルプログラムを示す。サンプルではINSERT,UPDATE,SELECT,SELECT ALLの4つの操作について、1万回処理した場合の処理時間を計測してファイル出力している。

◇サンプルコード
package application;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class TestDB
{
    // 接続文字列
    private static enum             USE_DB            { DB_DERBY , DB_H2 , DB_HSQLDB };
    private static final String[]   JDBC_STRING     = { "jdbc:derby:db/DerbyTest;create=true" ,                     // Derby用のDBフォルダ名
                                                        "jdbc:h2:file:./db/H2DB/H2DBTest"    ,                      // H2用のDBファイル名
                                                        "jdbc:hsqldb:file:db/HSQLDB/HSQLDBTest;shutdown=true"};     // HSQLDB用のDBファイル名                                                                      
    
    private static final String[]   USER_NAME       = { "test" , "" , "test" };     // 接続するユーザ名
    private static final String[]   PASSWORD        = { "test" , "" , "test" };     // ユーザ名に対応するパスワード
    private static final int        TRIAL           = 10000;                        // 時間計測用にループを回す回数
    
    /**
     * 起動関数
     * @param args
     * @throws FileNotFoundException 
     */
    public static void main(String[] args)
    {   
        // 使用するDBを決定
        USE_DB  use  = USE_DB.DB_DERBY;
        //USE_DB  use  = USE_DB.DB_H2;
        //USE_DB  use  = USE_DB.DB_HSQLDB;
        
        // 変数の宣言
        Connection  con     = null;     // コネクション
        Statement   stmt    = null;     // ステートメント
        String      sql     = null;     // SQL文字列
        long        time    = 0L;       // 時間計測用

        // DBに接続
        try {
            // 標準出力をファイルに変更
            File        file    = new File( String.format( "%s.log" , use  ) );
            PrintStream out     = new PrintStream( file.getAbsolutePath() );
            System.setOut( out );

            // メッセージ表示
            System.out.println( String.format( "[START]  used DB is %s. trial number is %d." , use , TRIAL ) );
            
            // コネクションの作成
            con = getConnection( use );

            // SQL発行(CREATE)
            stmt    = con.createStatement();
            sql     = "create table PERSON" +
                      "(" +
                      "     NAME VARCHAR(20) PRIMARY KEY," +
                      "     AGE  INTEGER "+
                      ")";
            stmt.executeUpdate( sql );

            // SQL発行(INSERT)
            time    = System.currentTimeMillis();
            for( int i=0 ; i<TRIAL ; i++ )
            {
                stmt    = con.createStatement();
                sql     = String.format( "insert into PERSON ( NAME , AGE ) values ( 'name%d' , %d )" , i , i );
                stmt.executeUpdate( sql );
            }
            System.out.println( String.format( "[INSERT] %d [ms]" , System.currentTimeMillis() - time ) );
            
            // SQL発行(UPDATE)
            time    = System.currentTimeMillis();
            for( int i=0 ; i<TRIAL ; i++ )
            {
                stmt    = con.createStatement();
                sql     = String.format( "update PERSON set AGE = 30 where NAME = 'name%d'" , i );
                if( stmt.executeUpdate( sql ) <= 0 ){ throw new Exception("SQLの実行に失敗(UPDATE)"); }
            }
            System.out.println( String.format( "[UPDATE] %d [ms]" , System.currentTimeMillis() - time ) );
            
            // SQL発行(SLECT)
            time    = System.currentTimeMillis();
            for( int i=0 ; i<TRIAL ; i++ )
            {
                stmt    = con.createStatement();
                sql     = String.format( "select * from PERSON where NAME = 'name%d'" , i );
                if( ! stmt.execute( sql ) ){ throw new Exception("SQLの実行に失敗(SELECT)"); }
            }
            System.out.println( String.format( "[SLECT]  %d [ms]" , System.currentTimeMillis() - time ) );
            
            // SQL発行(SLECT ALL)
            time    = System.currentTimeMillis();
            for( int i=0 ; i<TRIAL ; i++ )
            {
                stmt    = con.createStatement();
                sql     = "select * from PERSON";
                if( ! stmt.execute( sql ) ){ throw new Exception("SQLの実行に失敗(SELECT ALL)"); }
            }
            System.out.println( String.format( "[SELECT ALL] %d [ms]" , System.currentTimeMillis() - time ) );
            
            // 結果の処理
            ResultSet   rs      = stmt.getResultSet();
            while( rs.next() )
            {
                // レコード内の値を取得
                String  name    = rs.getString( "NAME" );
                int     age     = rs.getInt( "AGE" );
                System.out.println( String.format( "name is %s , address is %d ." , name , age ) );
            }
            
        } catch ( Exception e ) {
            e.printStackTrace();
        } finally {
            // DBを閉じる
            try {
                if( con != null ){ con.close(); }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 使用するDBを指定してコネクション作成
     * @param use
     * @return
     * @throws Exception
     */
    private static Connection getConnection( USE_DB use ) throws Exception
    {
        // 戻り値用変数
        Connection  con = null;
        
        // 使用する接続文字列・ユーザ名・パスワードを決定
        int i   = -1;
        switch( use )
        {
        case DB_DERBY:
            i = 0;
            break;
        case DB_H2:
            i = 1;
            break;
        case DB_HSQLDB:
            i = 2;
            break;
        default:
        }
        
        // 接続文字列・ユーザ名・パスワードを指定してDB接続
        con    = DriverManager.getConnection( JDBC_STRING[i] , USER_NAME[i] , PASSWORD[i] );
        
        // 戻り値
        return con;
    }

}
◇実行結果
[START]  used DB is DB_DERBY. trial number is 10000.
[INSERT] 28035 [ms]
[UPDATE] 38252 [ms]
[SLECT]  55602 [ms]
[SELECT ALL] 41762 [ms]
name is name0 , address is 30 .
name is name1 , address is 30 .
…略

◇解説

 プログラムの骨格を見ていくと、32行目で指定したDBに対しgetConnetion関数(53行目)でDBコネクションを取得し、SQLの発行と処理時間の表示を行っている(56行目~112行目)。SQLの発行はConnection::createStatement関数でStatementインスタンスを作成し、そこにSQL文字列を設定・実行するという流れになる。

 他に分かりにくい点としては、45行目~47行目で標準出力先としてファイルを指定している点くらいだと思われる。

◇性能比較
 利用するDBを変更して(32行目~34行目のコメント部を利用)、それぞれのDBで上記プログラムを起動した結果を以下に示す。embededにおける基本的な性能は HSQLDB > H2 >> Derbyといったところだろうか。H2のSelect ALLが異常に早く終了するのは、SQL発行の時点ではデータ取得を行っていないためだと思われる。歴史から想像するとHSQLDBよりもH2のほうが処理時間が短いと思ったが、結果はHSQLDBのほうが早かった。もちろんチューニング次第で早くなる可能性はあるが、他の利用者でも同様の性能順を示している模様なのでそのとおりなのだろう(*6,*7)。

表:DB別10000回のSQL実行時間[ms]
SQL操作 Derby HSQLDB H2
INSERT 28,035 1,419 1,930
UPDATE 38,252 1,327 2,278
SELECT 55,602 1,180 1,137
SELECT ALL 41,762 26,059 52


■ 参照

  1. Oracle 「Java DB 10.11」
  2. Apache Derby 「Documentation」
  3. HyperSQLDB
  4. HSQLDB使い方メモ
  5. H2DB
  6. JPA Performance Benchmark (JPAB)
  7. Stack Overflow 「Which is better H2 or HSQLDB? 
Home > Java > 埋込RDBの利用(Derby,HSQLDB,H2ライブラリの性能比較)

- ランダム記事 -
- PR -

コメント

プロフィール

管理者:
 連絡はContactよりお願いします。

PR