Play!Framework1.2.5でDBFlute1.0.0を使う

いささか旧聞ですが、2ヶ月ほど前にDBFluteのバージョン1がリリースされました。
DBFluteといえば、非常に強力で柔軟なO/Rマッパーです。

どれぐらい、強力で柔軟かというと、便利すぎて、DBFluteのv1.0リリースを記念として、社内DBFlute信者による布教活動勉強会が先日行われたほどです:

今回は、これを、JavaのPlay!Frameworkで使っちゃおうというのが、本記事です。

ただ、Play!Frameworkについては、最新の2.0ではなく、1.2系を使っています。
と言うのも、まだ2.0系とDBFluteの連携は試してないから、そもそも記事に出来ないという単純な理由です。

とまぁ、そんな前置きは置いておいて、とりあえず、進めていきましょう。

今回の環境

まず、今回の環境は以下の通りです。
特にDBサーバー等は予め用意しておいて下さい。

  • Play! Framwork 1.2.5
  • DBFlute 1.0.0
  • IDE:Eclipse
  • DBサーバー:postgres://postgres@dbServer/exampleDb (PostgreSQL9.2)

Play!Frameworkの初期設定

まずはPlay!Frameworkがなければ始まりませんので、公式サイトから最新の1.2.5をダウンロードします。
http://www.playframework.org/

適当な場所に解凍してPlayのプロジェクト作成です。

C:\Bancho\play-1.2.5>play new PlayTheDBFlute

今回は、DBFluteのDIコンテナとしてguiceモジュールを使うので、Playのguiceモジュールを入れてしまいます。(★ポイント1★)

.\PlayTheDBFlute\conf\dependencies.yml
のファイルをテキストエディターソフトで開きguiceモジュールを使う指定を書き加えます。

# Application dependencies

require:
  - play
  - play -> guice 1.2

そして、依存性の解決と、eclipsifyします。

C:\Bancho\play-1.2.5>cd PlayTheDBFlute
C:\Bancho\play-1.2.5\PlayTheDBFlute>..\play dependencies
C:\Bancho\play-1.2.5\PlayTheDBFlute>..\play eclipsify

ここまでやったら、今作ったプロジェクトをEclipseのインポートメニューで「既存プロジェクトをワークスペースへ」で取り込みます。

guiceモジュールの導入作業が入っていますが、ここまでは、至って普通のPlay! Frameworkの初期設定です。

DBFluteのインストール

次に、DBFluteのインストールです。

DBFluteのダウンロードや設定は、EclipseならばEMechaが定番ですので、DBFluteの公式(http://dbflute.seasar.org/ja/environment/setup/emecha.html)サイトの手順を参考に、EclipseにEMechaを入れて、DBFlute New Clientを行います。

この時の、DBFluteの設定ですが、今回はDIコンテナにguiceを使うのでguiceを指定します。そのため、設定画面はこのようなイメージになります。(★ポイント2★)

DBFluteの最新バージョンの確認と、ダウンロードを行って「完了」を押せば、DBFluteに必要なファイル群がプロジェクト内に用意されます。

DBFluteのインストールが終わったら、DBFluteの既定の場所にreplace-schema.sqlを用意して置くのも忘れずに。
今回は、非常にシンプルな、以下のようなテーブルを用意しました。

CREATE TABLE example_table (
  id serial PRIMARY KEY NOT NULL,
  message text NOT NULL
);

このあたりの設定はDBFluteユーザーには定番ですね。

DBFluteとPlay!の連携

ここからが、ある意味本題で、Play!上でDBFluteを使うための各種設定です。

DBFluteの各種クラスの生成

Play!が使用するソースファイルの場所と、DBFluteが各種ソースファイルを生成する場所は異なるため、これを同じ場所に設定しなおします。(★ポイント3★)
/dbflute_exampleDb/dfprop/basicInfoMap.dfprop
の設定例を見ながら、

; generateOutputDirectory = ../app

と書き換えましょう。

その上で、DBFluteの定番コマンド、

C:\Bancho\play-1.2.5\PlayTheDBFlute\dbflute_exampleDb> replace-schema.bat
C:\Bancho\play-1.2.5\PlayTheDBFlute\dbflute_exampleDb> jdbc.bat
C:\Bancho\play-1.2.5\PlayTheDBFlute\dbflute_exampleDb> generate.bat

の実行をすれば、先に用意したreplace-schema.sqlを元に、データベースが初期化されて、DBFluteのコードがプロジェクト内に生成されます。

このとき、ビルドエラーが出ていますが、原因は、dbfluteのライブラリが不足していることなので、Eclipseのプロジェクトのプロパティーで、
/mydbflute/dbflute-1.0.0/lib/dbflute-runtime.jar
をビルドパスに加えてあげれば、ビルドエラーは無くなるはずです。

DIの設定とコネクション管理

さて、DBFluteのコードが正常に生成されたので今度は、GuiceのDI対象にDBFluteを追加します。
DBFluteでは、この処理のためのクラスDBFluteModuleを自動生成しているため、これでGuiceの設定をします。

また、Play!には元々データベース操作の仕組みは持っています。
元はと言えば、「それを使うよりもDBFluteが使いたい!」という、わがままから始まったのが今回のお話。

さらにわがままを言うと、「データベースの接続管理やExceptionが出たときのRollback等は、Play!が元々持っている機能に任せたい!」とわがままをさらに言ってみたいです。

これらの、諸問題(?)を解決するために、以下のようなクラスを作ります。(★ポイント4★)

package framework.guice;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import org.seasar.dbflute.jdbc.DataSourceHandler;

import play.db.DB;
import play.modules.guice.GuiceSupport;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;

import dbflute.allcommon.DBFluteConfig;
import dbflute.allcommon.DBFluteModule;

/**
 * GuiceにDBFluteのクラス群を利用できるようにする。
 * また、その際に、DBFluteのデータベース接続を
 * Playのコネクション管理を利用するように設定する。
 */
public class GuiceConfigure extends GuiceSupport {
  /*
   * (非 Javadoc)
   * @see play.modules.guice.GuiceSupport#configure()
   */
  @Override
  protected Injector configure() {
    // まず、DBFluteのDBのコネクションを、Playの管理するものにする。
    DBFluteConfig config = DBFluteConfig.getInstance();
    config.unlock();
    config.setDataSourceHandler(new DataSourceHandler() {
      @Override
      public Connection getConnection(DataSource source) throws SQLException {
        return DB.getConnection();
      }
    });
    config.lock();
    // DBFluteのクラス群をDIコンテナに認識させる。
    List<Module> moduleList = new ArrayList<Module>();
    moduleList.add(new DBFluteModule(DB.datasource));
    return Guice.createInjector(moduleList.toArray(new Module[]{}));
  }
}

これで、DBFluteをPlay!のコード上でDI出来るようになり、さらにデータベースの接続の設定はPlay!Frameworkに任せることが出来るようになりましたので、データベースの接続情報をPlay!の設定ファイルに書き込んでおきましょう。

/conf/application.conf

db=postgres://postgres@dbServer/exampleDb

を書き加えます。

以上、めでたしめでたし・・・
と思いきや、まだ問題があります。

実はこのままでは、一見動くのですが、1回のリクエストでSQLを2回実行するとエラーになるなど、方々で問題が起きます。

そこで以下のように、@javax.persistence.Entity で注釈したダミークラスを用意します。このアノテーションが付いたクラスが存在すると、Play!FrameworkはJPA エンティティマネージャを開始し、その実装でDBFluteとの接続の諸問題も解決されます。(★ポイント5★)
※詳しい説明は割愛。詳しくはDBクラスのgetConnectionメソッドあたりを辿って見て下さい。

package models;

import javax.persistence.Entity;
import play.db.jpa.Model;

/**
 * DBFluteがPlay!FrameworkのDBを利用する諸問題を解決するためのクラス。
 */
@Entity
public class Dummy extends Model {
}

実際に使ってみる

では、実際に使ってみます。
使用方法は難しくありません。DBFluteを使いたいクラスに対して、@InjectSupportのアノテーションを付加し、DBFluteのBehaviorのプロパティに@Injectのアノテーションを付加。あとは普通にDBFluteを使うだけです。

@play.modules.guice.InjectSupport
public class SampleModel {
  @javax.inject.Inject
  private static ExampleTableBhv exampleTableBhv;
  public void check() {
    ExampleTableCB cb = exampleTableBhv.newMyConditionBean();
    int count = exampleTableBhv.selectCount(cb);
    if (count == 0) {
      //適当な処理
    }
    ExampleTable ent = exampleTableBhv.newMyEntity();
    ent.setMessage("This is test message!");
    exampleTableBhv.insert(ent);
    // 例外発生時には自動的にロールバックするが、手動でロールバックする場合は以下のようにする
    // JPA.setRollbackOnly();ロールバックする場合
    return ;
  }
}

以上、駆け足でPlay!Framework1.2系とDBFluteとの連携手順でした。