M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

Apache Derbyを組み込みDBとして使う

Apache Derbyは、Javaで実装されたサーバ/クライアント型でも組み込み型でも使用できるデータベース。

今回は公式のマニュアルを参考に、以下の処理を行うプログラムを作ってみた。
まあようするに何の役にも立たないお勉強用プログラム・・・。

  • データベースが存在しなければこれを作成する。
  • データベースに必要なテーブルが存在しなければこれを作成する。
  • データベースに接続し、SELECT/INSERTを行う。

作成手順は以下の通り。

  1. 公式サイトのダウンロード・ページから、最新バージョンのDerbyを取得。今回は直近の2013年4月15日にリリースされたバージョン10.10.1.1。
  2. Eclipseで適当なJavaプロジェクトを作成。プロジェクト・ディレクトリ内にlibディレクトリを作成。
  3. 先にダウンロードした db-derby-10.10.1.1-bin.zip を解凍すると作成されるフォルダに、libディレクトリがある。このディレクトリ内にある、derby.jar と derbyLocale_ja_JP.jar を、前述のJavaプロジェクト内のlibにコピー。プロジェクトのビルドパスに追加する。
  4. あとは適当にmainメソッドを持つJavaクラスを作成して、Derbyに接続して処理を行うプログラムを書くだけ。

プロジェクトの構成はこんな↓感じになった。

f:id:m12i:20130427200204p:plain

そしてプログラムの方はこんな↓感じ。

package com.m12i.java.derby.study;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;

public class DerbyStudy {

  public static void main(String[] args) throws SQLException {
    // ドライバ名
    final String driverName = org.apache.derby.jdbc.EmbeddedDriver.class.getCanonicalName();
    // DB名(カレントディレクトリにこの名前でDBインスタンス=ディレクトリが作成される)
    final String dbName = "jdbcDemoDB";
    // DB接続文字列(create=trueでDBが存在しない場合新規作成するように指定している)
    final String connectionURL = "jdbc:derby:" + dbName + ";create=true";

    try {
      // ドライバをロードする
      Class.forName(driverName);
      // DB接続オブジェクトを参照する変数
      Connection conn = null;

      try {
        // DB接続オブジェクトを取得
        conn = DriverManager.getConnection(connectionURL);

        // DBが初期化されているかどうかを確認
        if (!wasDbInitialized(conn)) {
          // 初期化されていない場合は初期化処理が必要
          
          // アプリで使用するあれこれのDBオブジェクトを定義
          // ...
          
          // 最後に、初期化判断に使用しているLOGテーブルを作成
          final Statement s = conn.createStatement();
          s.execute("create table LOG (" +
              " ID        BIGINT    generated always as identity constraint LOG_PK primary key" +
              ",TIMESTAMP TIMESTAMP with default CURRENT_TIMESTAMP" +
              ",MESSAGE   VARCHAR(2000)" +
              ")");
        }
        
        // DBの準備はできたので引き続きあれこれ...
        doSomething(conn);

      } catch (SQLException e) {
        // 接続に失敗
        System.out.println(e.getMessage());
        
      } finally {
        // 何はともあれDB接続をクローズ
        if (conn != null) conn.close();
      }

    } catch (java.lang.ClassNotFoundException e) {
      // ドライバのロードに失敗
      System.out.println(e.getMessage());
    }

  }

  public static boolean wasDbInitialized(Connection conn) throws SQLException {
    // チェック用クエリが返す結果セットを参照するための変数
    ResultSet rs = null;
    try {
      // データベースが初期化されているかどうかを検証するためUPDATE文を発行
      final Statement s = conn.createStatement();
      // LOGテーブルの件数をカウント
      // ※ここではこれ以上のチェックをしないことにする(テーブルのあるなしのみで判断することにする)
      rs = s.executeQuery("select count(1) from LOG");
      
    } catch (SQLException e) {
      // 例外がスローされた。エラーコードを確認して処理を分岐
      
      // SQLステートコードを取得。
      final String state = e.getSQLState();
      
      // ステートコードで処理を分岐。
      if (state.equals("42X05")) {
        // テーブルが存在しない。データベースはまだ初期化されていない。
        return false;
      } else if (state.equals("42X14") || state.equals("42821")) {
        // テーブル定義が不正。テーブルは存在するも想定した定義ではない...
        throw e;
      } else {
        // その他の予期せぬ例外が発生...
        throw e;
      }
      
    } finally {
      // ともかくもResultSetをクローズ
      if (rs != null) rs.close();
    }
    // テーブルが存在した。(=すでに初期化されている。)
    return true;
  }

  private static void doSomething(Connection conn) throws SQLException {
    // 試みに、LOGテーブルへのINSERTを実施してみる
    final PreparedStatement insert = conn
        .prepareStatement("insert into LOG(MESSAGE) values (?)");
    insert.setString(1, String.format("Execute at %s.", new Date()));
    insert.execute();
    
    // 試みに、LOGテーブルからのSELECTを実施してみる
    final ResultSet rs = conn.createStatement()
        .executeQuery("select * from LOG order by ID");
    while (rs.next()) {
      System.out.println(
          String.format("ID=%s, TIMESTAMP='%s', MESSAGE='%s'",
          rs.getString(1), rs.getString(2), rs.getString(3)));
    }
  }
}

ふむ・・・。データベースが初期化済みか否かの判断はもう少しスマートな方法がありそうだけど。とにかく公式マニュアルに紹介されたサンプル・プログラムではこういうことをしていた。

このプログラムは初回起動時にはデータベースとテーブルを新規に作成、その後は実行するたびにテーブルへのINSERTを繰り返す。実行結果の標準出力は以下のようになる。

ID=1, TIMESTAMP='2013-04-27 19:47:12.703', MESSAGE='Execute at Sat Apr 27 19:47:12 JST 2013.'
ID=2, TIMESTAMP='2013-04-27 19:47:17.212', MESSAGE='Execute at Sat Apr 27 19:47:17 JST 2013.'
ID=3, TIMESTAMP='2013-04-27 19:49:48.608', MESSAGE='Execute at Sat Apr 27 19:49:48 JST 2013.'
ID=4, TIMESTAMP='2013-04-27 19:50:16.914', MESSAGE='Execute at Sat Apr 27 19:50:16 JST 2013.'