S2JDBCをEntityの親クラスのプロパティまで見るように拡張

=== 1/31 追記 ===
@MappedSuperclassで出来るようです。
http://s2container.seasar.org/2.4/ja/s2jdbc_entity.html#%E7%B6%99%E6%89%BF

higayasuoさんからコメントで教えていただきました。ありがとうございました。



バイト先でのO/R Mapperの教育や個人的な開発にS2JDBCを使っています。シンプルだし、Hibernateでダメダメだった複数の1対多関連の紐付けがうまくいっていたりと気に入ってるんですが、シンプルがゆえに物足りなさを感じることも。

今のところ物足りないなぁと感じているのは、

  1. S2JDBCはEntityの親クラスのプロパティを評価してくれないのでジェネレーション・ギャップパターンが出来ない。
  2. ページング検索をするときに、ある検索条件+limit, offsetで検索、そのあとある検索条件をselect count(*) form (…)でかこってトータルを取得というのができない。つまり、S2Pagerみたいなこと。

2.については俺が知らないだけかも。。

1.は結構困っていたので今回自力で拡張してみました。
同じような要望を持ってる人もいるみたい。
http://ml.seasar.org/archives/seasar-user/2007-December/012363.html

アプローチは、上記のリンクでひがさんが言ってるように、S2JDBCとその周辺のクラスは拡張しやすいように作ってあるのでフレームワークのクラスを継承して拡張します。

JdbcManagerImplから辿ってクラスを眺めていたら、JdbcManagerがEntityMedaDataFactoryを持っていて、そこからEntityの情報を取得している様子。こいつの実装クラスのEntityMedaDataFactoryImplがメタデータを作成&キャッシュしているので、こいつを継承することにした。

public class CustomizedEntityMetaFactoryImpl extends EntityMetaFactoryImpl {

	@Override
	protected void doCustomize(EntityMeta entityMeta, Class<?> entityClass) {
		super.doCustomize(entityMeta, entityClass);
		
		// エンティティの継承をサポートする
		doSuperClassPropertyMeta(entityMeta, entityClass);
	}
	
	private void doSuperClassPropertyMeta(EntityMeta entityMeta, Class<?> entityClass) {
		Class<?> clazz = entityClass.getSuperclass();
		while (!clazz.equals(Object.class)) {
			super.doPropertyMeta(entityMeta, clazz);
			clazz = clazz.getSuperclass();
		}
		
	}
}

doPropertyMetaというメソッドがあって、そこでEntityのプロパティをなめてるからそいつをoverrideしようかとも迷ったんだけども、doCustomizeという空のメソッドがあったのでカスタマイズしろ!というこだと空気を読んでこっちにした。スーパークラスのなめかたはもっといい方法があったら教えてください< > <

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
    "http://www.seasar.org/dtd/components24.dtd">
<components>
    <include path="jdbc.dicon"/>
    <include path="convention.dicon"/>
    <include path="s2jdbc-internal.dicon"/>
    
	<component class="my.s2jdbc.CustomizedEntityMetaFactoryImpl" name="entityMetaFactory" >
    </component>
    <component name="jdbcManager"
      class="org.seasar.extension.jdbc.manager.JdbcManagerImpl">
        <property name="maxRows">0</property>
        <property name="fetchSize">0</property>
        <property name="queryTimeout">0</property>
        <property name="dialect">hsqlDialect</property>
    </component>
</components>

s2jdbc.diconをこんな感じに。s2jdbc-internal.diconでEntityMetaFactoryImplが既に登録されてるから、ひとつのinterfaceに対して2つの実装クラスが登録されているあまりよくない状態になってる。でも、s2jdbc-internal.diconとかあんまいじりたくないし! > < 一応name属性による解決が先にされるはずなので、うまくいってるんだと思う。



これで、Entityクラスの継承が可能に!!doltengで作成したプロジェクトについてるEmpテーブルで試してみました。

public abstract class EmpBase implements Serializable {
	
	@Id
	public Integer id;
	public Integer empNo;
	public String empName;
	public Integer mgrId;
	@Temporal(TemporalType.DATE)
	public Date hiredate;
	public BigDecimal sal;
	public Integer deptId;
	@Version
	public Integer versionNo;
}

@Entity
public class Emp extends EmpBase {

	private static final long serialVersionUID = -1L;
}

public class Test extends S2TestCase {

	JdbcManager jdbcManager_;
	
	@Override
	protected void setUp() throws Exception {
		include("app.dicon");
		super.setUp();
	}
	
	public void testGap() {
		List<Emp> emps = jdbcManager_.from(Emp.class)
				.getResultList();
		System.out.println(emps);
	}
}

こんな感じでやったらうまくいってました。やったね!