Spring Boot で組み込みDerbyデータベースを使う

【2016/06/14 追記】
JPAの部分は、Spring Data JPAの機能を利用するとpersistence.xmlを書かなくても動かせるはずです。コードを直したら、本ブログも修正する予定です。 コメント頂いた@makingさん。ありがとうございます。


先日のJava Day Tokyo 2016では、ショートハッカソンのセッションがあったのですが、そちらの準備と一部講師を担当させていただいてました。
3時間という、一般的なハッカソンに比べるとかなり短い枠でしたので、さすがに一からモノを作ってもらうわけには行かず、予めサンプル・アプリケーションを作っておいてそれをカスタマイズしてもらうという形態をとりました。

サンプルアプリを作っていく中で、Spring bootのアプリ内で組み込みDerby(読み取り専用)を動かすという技を習得したので、やり方を書きたいと思います。

ポイント

今回のアプリでは、DBのバイナリ/データを含む全ての依存成果物を、1つのjarに収める必要がありました。
Application Container Cloud(ACC)というOracleのコンテナ型のPaaSを利用するハッカソンでしたので、マイクロサービスらしくアプリをひとつにまとめたかったためです(本当は外部にDBを設けることもできます)。

これを実現するため、以下の2点を抑える必要がありました。

  1. Derbyのバイナリ/データファイルをjarに組み込む
  2. サンプルアプリから同じJVM上のDerbyに接続する

これらを踏まえて、以下、手順にいきます。

手順

1. データファイルの作成

はじめに、通常の手順でDerbyのデータベースを作成し、データを投入します。

以下の例では、ijを使ってC:\temp\dbフォルダ配下にhrというデータベースを作成し、同時に接続しています。ijは、DerbyのCLIクライアント(Oracle Databaseのsqlplusのようなもの)です。

> cd ${JAVA_HOME}\db\bin
ijバージョン10.11
> ij.bat
ij> connect 'jdbc:derby:C:\temp\db\hr;create=true';

続いてデータを投入していきます。例えば、create_tables.sql、populate_tables.sqlというスクリプトを使う場合、以下のコマンドを実行します。

ij> run 'C:\temp\db\scripts\create_tables.sql';
…
(実行結果の出力)
…
ij> run 'C:\temp\db\scripts\populate_tables.sql';
…
(実行結果の出力)
…

データの投入が終わったら接続を解除します。

ij> exit;

データファイルは接続時に指定したパスに作成されています。ディレクトリ構成は以下のようになっています。

C:\temp\db
└── hr
    ├── log
    │   ├── log.ctrl
    │   ├── log1.dat
    │   ├── logmirror.ctrl
    │   └── README_DO_NOT_TOUCH_FILES.txt
    ├── README_DO_NOT_TOUCH_FILES.txt
    ├── seg0
    │   ├── c10.dat
    │   ├── cf0.dat
    │   ├── …(.datがたくさん)
    │   └── README_DO_NOT_TOUCH_FILES.txt
    └── service.properties

次に、上記のファイル群の中にあるservice.propertiesを編集します。これはDerbyデータベースの設定を記述するファイルです。

derby.storage.tempDirectoryという設定を追記して、一時ファイルの出力先を変更します。
デフォルトでは、データファイル群のトップのフォルダ(上記のhrフォルダ)に一時ファイルが出力されますが、今回はそのフォルダはjarの中に格納されてしまいます。そのため、書き込み可能な領域(jarファイルの外)を一時ファイルの出力先にしておきます。

以下の例は、プログラムの実行カレント以下、temp/db/hrフォルダに設定しています。

SysschemasIndex2Identifier=225
SyscolumnsIdentifier=144
…
derby.storage.tempDirectory=temp/db/hr (←追記)

最後に、データファイル群をMavenプロジェクトの配下にコピーします。 今回の場合、src/main/resources/の下にdataという階層を切ってファイルを配置しました。こうしておくと、ビルドしたときに、jarファイル内のトップにdata/hrというフォルダ構成が作られてデータファイルが格納されます。

Mavenプロジェクト内のデータファイルの階層

[プロジェクトトップ]
├─src
│  ├─main
│  │  ├─java
│  │  │  └─…
│  │  └─resources
│  │      ├─data
│  │      │  └─hr
│  │      │      ├─log
│  │      │      └─seg0
│  ├─…  ├─…

jarファイル内のデータファイルの階層

xxx.jar
├─data
│  └─hr
│      ├─log
│      └─seg0
├─lib
├─…

2. JPAの設定

データベースにアクセスするには、クラスパスを使ったDBアクセス*1を利用します。
これは、データファイルをパッケージしたjarをクラスパスに入れておいて、クラスパス中のDBにアクセスする方法です。ただし、今回はアプリケーション本体のjarにデータファイルが入っていますので、新たにクラスパスを追加する必要はありません*2

注意すべきは、JDBC URLが、この方法特有の書き方になることと、JDBCドライバーにEmbeddedDriverを指定することです。

今回の場合、ビルド成果物のjar直下にdata/hrというパスでデータファイルが格納されています。それに合わせて、persistence.xmlに以下のように記述します。

    <persistence-unit name="HR" transaction-type="RESOURCE_LOCAL"><properties>
            <property
                name="javax.persistence.jdbc.url"
                value="jdbc:derby:classpath:data/hr"/>
            <property
                name="javax.persistence.jdbc.driver"
                value="org.apache.derby.jdbc.EmbeddedDriver"/>
        </properties></persistence-unit>

ユーザー名/パスワードは、データベースを作成した時に設定していれば記述する必要があると思われますが、とくに何もしていなければ不要です。

3. ビルド

pom.xmlには、依存モジュールにderbyのjarを追加しておきます。

    <dependencies>
        …
        <dependency>
            <groupId>org.apache.derby</groupId>
            <artifactId>derby</artifactId>
            <scope>compile</scope>
        </dependency>
    </dependencies>

あとはビルドして、アプリを実行します。

> cd [pom.xmlのあるフォルダ]
> maven package
> java -jar target/[jarファイル名]

まとめ

実際にハッカソンで使ったアプリケーションは、GitHubにコードがあります。今回の手法の実装例として、ご参考にどうぞ。

Derbyのマニュアルのトップはこちら

*1:Accessing databases within a jar file using the classpath

*2:この機能は、データファイルだけのjarを作っておいて、それをクラスパスに入れるという使い方が想定されているようです。しかし、データファイルのjarを作っておいてSpring Bootアプリのjarに入れる構造にすると、クラスパスからデータファイルのjarが見えなくなってしまい、DBにアクセスできません。