S2JDBCを使った再帰構造テーブルのデータを取得する

S2JDBCって再帰構造テーブルのデータを取得できるみたいでなのでやってみます。

親子構造を持つデータで、「業種」なるものを例にとってまずはテーブル作成から

テーブル
--シーケンス作成
create sequence "category_seq" start 100;
--テーブル作成
CREATE TABLE category
(
  id integer NOT NULL DEFAULT nextval('category_seq'::regclass),
  parent_id integer,
  "name" character varying(100),
  version_no integer,
  CONSTRAINT category_pkey PRIMARY KEY (id),
  CONSTRAINT fk_category_recursion FOREIGN KEY (parent_id)
      REFERENCES category (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)

再帰構造でデータを取得するためにやることは、エンティティとサービスにちょいと仕込んであげるだけでOKです。

Entity

親エンティティへのマッピングを追加します。

@Entity
public class Category {
    @Id
    @GeneratedValue
    public Integer id;

    @Column(name="parent_id")
    public Integer parentId;

    public String name;
    // 親データとなるエンティティをマッピング
    @ManyToOne
    public Category parent;
}
Service

実際S2JDBCをコールするサービスでは、親エンティティとしてマッピングしたプロパティをjoinします。

@Override
public List<Category> findAll() {
	return super.select().leftOuterJoin("parent").getResultList();
}

こだけで、カテゴリーサービスのfindAllをコールすると、親エンティティがセットされた状態のリストが取得できます。
ちなみに発行しているSQLは1回のみで、あとはS2JDBCの実装クラス(org.seasar.extension.jdbc.query.AutoSelectImpl)内にて
エンティティに設定しているマッピング情報を元にいろいろとやってくれているみたいです。

ルート(親)、もしくは条件指定するとリレーションのプロパティにセットされない?

しかし、どうやらwhere句などを指定するとどうもプロパティにセットされずに再帰構造にはならないみたいですね。

これは、エンティティに格納するハンドラが、条件が指定されているとされていない場合にそれぞれ違うインスタンスが指定されるからみたいです。

***selectAllにて条件指定なしの場合
org.seasar.extension.jdbc.handler.BeanListAutoResultSetHandler

***selectAllにて条件指定あり、もしくは外部SQLファイルの場合
org.seasar.extension.jdbc.handler.BeanListResultSetHandler


ま〜、条件指定すると、きれいな再帰構造を構成するのに情報が欠落してる可能性とかあるからかのかな、

上記の件に関しては僕の検証が足りなかったみたいで、Where句を使用してもBeanListAutoResultSetHandlerでハンドリングされます。

再帰SQL

それならばと、SQL99にて導入されている再帰SQLを使えばよいではないかと、ためしにやってみる。

再帰SQL(src/main/resources/sql/category/findByRoot.sql
WITH RECURSIVE tmp_category as (
  select * from category where id = /*id*/100
  UNION ALL
  select category.* from tmp_category, category where category.parent_id = tmp_category.id

)
select * from tmp_category;
Service

実際S2JDBCをコールするサービスでは、親エンティティとしてマッピングしたプロパティをjoinします。

public List<Category> findByRoot(Integer id) {
	Map<String, Object> param = new HashMap<String, Object>();
	param.put("id", id);
	return this.jdbcManager
		.selectBySqlFile(Category.class, "sql/category/findByRoot.sql", param)
		.getResultList();
}

結果としてはマッピングはされていなかったです。
SQL自体は問題なかったんだけど、格納時のハンドらがBeanListResultSetHandlerになっていた。

なんとかして再帰SQLマッピングを。。。

追加

org.seasar.extension.jdbc.query.AutoSelectRecurrenceImpl
これは書くと長すぎるので省きます。
(ベースはorg.seasar.extension.jdbc.query.AutoSelectImplをコピってます)

修正

以下のそれぞれのクラスに必要なメソッドを追記します
org.seasar.extension.jdbc.JdbcManager

/**
 * 再帰データの検索を作成します。
 *
 * @param <T>
 *            戻り値のベースの型です。
 * @param baseClass
 *            ベースクラス
 * @return 自動検索
 */
<T> AutoSelectRecurrence<T> fromRecurrence(Class<T> baseClass);

org.seasar.extension.jdbc.manager.JdbcManagerImpl

public <T> AutoSelectRecurrence<T> fromRecurrence(Class<T> baseClass) {
    return new AutoSelectRecurrenceImpl<T>(this, baseClass).maxRows(maxRows)
            .fetchSize(fetchSize).queryTimeout(queryTimeout);
}

jp.co.hoge.service.AbstractService

/**
 * 再帰構造データを取得する際に使用するセレクタです
 *
 *
 * @return AutoSelectRecurrence 再帰取得用セレクタ
 */
protected AutoSelectRecurrence<ENTITY> selectRecurrence()
{
    return jdbcManager.fromRecurrence(this.entityClass);
}

これで何とか親情報のIDを指定して、再帰構造でデータを取得できるようになったが、
JdbcManagerやJdbcManagerImplなどに手を加えることになってしまった。