T2+Spring+Domaのサンプル

シーサーカンファレンスの時に、id:taediumさんに「俺、戦争が終わったらDomaのサンプル作るんだ」と宣言していたので、T2+Spring+Domaのサンプルを作成しました。


ソース

SVNを使用する場合は、「http://t-2.googlecode.com/svn/trunk/samples/t2-spring-doma」からチェックアウトできます。
またアーカイブ形式のものは、「http://t-2.googlecode.com/files/t2-spring-doma-0.6.1-ga.zip」からダウンロードできます。
いずれもeclipseプロジェクト形式になっています。


eclipseを使用する場合は、eclipse3.5以上を使用してください。(domaのapt機能が3.5以上でないと動作しないため)


ダウンロード後にリビルドして、bootstrap/samples/SpringDomaSampleServerStart.javaJava実行すると、アプリが動作します。

構成

典型的なEmployeeアプリケーションです。ビュー側をT2、データベースをDomaコンポーネント管理をSpringで行っています。
トランザクション設定もしてあり、サービスクラスがトランザクション境界になっています。

T2とSpringの連携

T2とSpringは、T2で用意しているSpringAdapter(springadapter-0.6.1-ga.jar)を使用しています。
web.xmlのT2Filterのパラメータに

  <init-param>
    <param-name>t2.container.adapter</param-name>
    <param-value>org.t2framework.t2.adapter.SpringAdapter</param-value>
  </init-param>

と書く事で、Springと連携できます。


Springの設定ファイルは、

  <init-param>
    <param-name>t2.config</param-name>
    <param-value>applicationContext.xml</param-value>
  </init-param>

で指定できます。

SpringとDomaの連携

Domaと連携するためには、Spring管理のDataSourceをDomaに渡す事と、Domaが生成したDao実装クラスをSpringで管理する必要があります。
ということで、まずはDomaが生成する実装クラスを見て行きます。


Daoインターフェースがこのような形の場合、

@Dao(config=SpringDomaConfig.class)
public interface PersonDao {

	@Select
	List<Person> findAll();


実装クラスはこのように生成されます。

@javax.annotation.Generated(value = { "Doma", "0.9.6" }, date = "2009-10-11 00:32:10")
public class PersonDaoImpl extends org.seasar.doma.jdbc.DomaAbstractDao implements org.t2framework.samples.spring.dao.PersonDao {

    public PersonDaoImpl() {
        super(new org.t2framework.samples.spring.config.SpringDomaConfig(), null);
    }

    public PersonDaoImpl(javax.sql.DataSource dataSource) {
        super(new org.t2framework.samples.spring.config.SpringDomaConfig(), dataSource);
    }

    protected PersonDaoImpl(org.seasar.doma.jdbc.Config config) {
        super(config, config.dataSource());
    }
    @Override
    public java.util.List<org.t2framework.samples.spring.entity.Person> findAll() {
        entering("org.t2framework.samples.spring.dao.PersonDaoImpl", "findAll");
        org.seasar.doma.internal.jdbc.query.SqlFileSelectQuery query = new org.seasar.doma.internal.jdbc.query.SqlFileSelectQuery();
        query.setConfig(config);
        query.setSqlFilePath(org.seasar.doma.internal.jdbc.sql.SqlFileUtil.buildPath("org.t2framework.samples.spring.dao.PersonDao", "findAll"));
        query.setCallerClassName("org.t2framework.samples.spring.dao.PersonDaoImpl");
        query.setCallerMethodName("findAll");
        query.setQueryTimeout(-1);
        query.setMaxRows(-1);
        query.setFetchSize(-1);
        query.prepare();
        org.seasar.doma.internal.jdbc.command.SelectCommand<java.util.List<org.t2framework.samples.spring.entity.Person>> command = new org.seasar.doma.internal.jdbc.command.SelectCommand<java.util.List<org.t2framework.samples.spring.entity.Person>>(query, new org.seasar.doma.internal.jdbc.command.EntityResultListHandler<org.t2framework.samples.spring.entity.Person>(new org.t2framework.samples.spring.entity.Person_()));
        java.util.List<org.t2framework.samples.spring.entity.Person> result = command.execute();
        exiting("org.t2framework.samples.spring.dao.PersonDaoImpl", "findAll", result);
        return result;
    }


@Daoのconfig属性で指定したクラス(SpringDomaConfig.class)が、Dao実装クラスのコンストラクタインスタンス化されます。
このSpringDomaConfigクラスは、Domaが用意しているDomaAbstractConfigクラスを継承しています。
Domaは、このクラスからDataSourceとDialect(DBの方言)などの情報を取得します。なので、このクラスをSpringとつなげれば、うまくDomaと連携できます。


SpringDomaConfigの実装はこのようになっています。

@Component
public class SpringDomaConfig extends DomaAbstractConfig {

	private static DataSource dataSource;
	private static Dialect dialect;

	@Autowired
	public void setDataSource(DataSource dataSource) {
		SpringDomaConfig.dataSource = dataSource;
	}

	@Autowired
	public void setDialect(Dialect dialect) {
		SpringDomaConfig.dialect = dialect;
	}

	@Override
	public DataSource dataSource() {
		return dataSource;
	}

	@Override
	public Dialect dialect() {
		return dialect;
	}

}

DataSourceとDialectはあらかじめSpringに登録しておき、さらにSpringDomaConfigもSpringに登録しておきます。
Springは起動時にコンポーネントインスタンス化して@Autowiredに対してDIを実行するため、static領域にSpringからDIされたインスタンスを入れておくことが出来ます。
従って、Dao実装クラスのコンストラクタでSpringDomaConfigがnewされたときには、dataSourceとdialectが取得できます。これで無事にDataSourceをDomaに渡せます。
(ほんとはDomaが生成するDao実装クラスにSpringのアノテーションがついていて、各Dao実装クラスにコンストラクタインジェクションするのが望ましいのですが、そうなるとDoma自体に手を入れる必要があるため、今回はこの手を使用することにしました。)


またDao実装クラスのSpringへの登録ですが、

  <!-- .*DaoImplのクラス名パターンで、Domaの生成したDao実装をSpringに登録する -->
  <context:component-scan base-package="org.t2framework.samples.spring.dao">
    <context:include-filter type="regex" expression=".*DaoImpl"/>
  </context:component-scan>

という感じで、正規表現を使用して一括で登録するようにしました。
Springに登録することで、DaoにAOPしたり、Daoをトランザクション境界を設定することが出来ます。

DomaとSpringJDBCの連携

DomaにはDelegate機能というのがついていて、一部処理をほかのクラスに委譲するようなことができるようです。
Domaはselect系はSQL、更新削除系は生成したクラスでというポリシーのようですが、ゴリっとJDBCJavaコードで書きたくなるシチュエーションも想定して、このような機能をついてるのかなと思います。
私はSpringJDBCが好きなので、SpringJDBCに委譲するような実装をしてみました。


まず委譲先の設定ですが、Daoインターフェースにアノテーションで行います。

@Dao(config=GuiceDomaConfig.class)
public interface PersonDao {

    //委譲先のクラスを指定
    @Delegate(to = PersonDaoDelegate.class)
    List<Person> getPersonsByName(String name);


次に、委譲先クラスを実装します。

public class PersonDaoSpringJDBCDelegate extends SimpleJdbcDaoSupport {

	private String sql = "select * from Person where name like ?";

	public PersonDaoSpringJDBCDelegate(Config config) {
		//DataSourceをDomaから取得
		setDataSource(config.dataSource());
	}

	public List<Person> getPersonsByName(String name) {

		String likeName = "%" + name + "%";

		ParameterizedBeanPropertyRowMapper<Person> rowMapper = ParameterizedBeanPropertyRowMapper
				.newInstance(Person.class);

		return getSimpleJdbcTemplate().query(sql, rowMapper, likeName);

	}
}


委譲先クラスは、特別なクラス・インターフェースを実装する必要はありません。委譲元のメソッドシグネチャが同じメソッドを実装すれば呼び出しを受けられます。
(委譲先に対応するメソッドがない場合、Domaがエラーを出してくれます)
またコンストラクタにorg.seasar.doma.jdbc.Configのインスタンスを引数に宣言しておけば、DomaがConfigのインスタンス(今回はSpringDomaConfigのインスタンス)を渡してくれます。


今回はSpringJDBCと連携するため、SimpleJdbcDaoSupportを継承します。
SimpleJdbcDaoSupportはDataSourceをセットする必要があるため、ConfigからDataSourceを取得してセットします。
あとは通常のSpringJDBCと同様に使用できます。

使ってみた感想

Doma自体は他ライブラリへの依存もなく、ほかのプロダクトとの組み合わせも出来るので、自分的にはとても使いやすく好感触だなと思いました。
T2自体も組み合わせられるような指向なので、そういう意味でも自分の指向にあっていたと思います。
SpringJDBCは更新系のサポートが弱く、単純なインサートとかは生成されたコードでやりたいと思っていたので、一部分だけDomaの機能を借りるとかが出来るのはよいと思いました。


あと、ハマッた点や気になった点などちょっとあげておきます。

Eclipseのバージョン

はじめeclipse3.3でやっていて、うまく動きませんでした(サイトみればよかったんですが)。
FlexBuilderが3.4までしか動かないので、3.4で動いてくれるとありがたいです。


追記:eclipse3.4では動作しました。

検索系のSQLファイル

検索系のメソッドSQLファイルがないとエラーになるのですが、SQLファイルまで作成してくれるといいなと思いました。
(中身が空の状態のSQLファイルを生成して、「中が空です」っていうエラーが出ると親切かも)
あとこれはポリシーなんだと思いますが、いまはSQLファイルなしだとエラーになってしまいますが、生成コードでの検索もあるとありがたいなと思いました。
(例えばデフォルトではSQLファイルがない/空だとエラーにして、アノテーションや属性で指定すると生成コードもありにするとか)

SQLファイルの更新

SQLファイルがない状態でSQLファイルを作成してもエラーが解消されないので、その場合は再ビルドが必要です。(aptの仕様だと思いますが)

生成されたクラスのソースを見る

生成されたクラスは.apt_generatedというフォルダに入るため、eclipseのツリーからは選択できません。
生成されたソースを見たい場合や、デバッグをしたい場合は、Daoインターフェースの型階層を出すと出てきます。

SQL

「select * from Person where id=/*id*/」だけだとうまく動かなくてちょっとはまりました(例外がUnsupport何とかだったので、ちょっとわかりにくかったです)。
「select * from Person where id=/*id*/'1'」が正解。

SQLファイル

パッケージ/クラス名/メソッド名.sqlの配置は大変わかりやすいです。またDBごとの切り替えはメソッド名_DB名.sqlで出来るので、これも大変ありがたいです。




ということで個人的には結構ツボった感じのDAOライブラリです。T2と共に一度お試ししてみては如何でしょうか?