S2JDBCを改造(order byを柔軟に設定できるようにする)

参考

通常のorder by 指定

例えばここで使ったようなテーブルがあるとして、
postgresqlだと

create table employee (
  id SERIAL NOT NULL, 
  name TEXT NOT NULL
);

id順に取得したい場合はSQL

SELECT * FROM employee ORDER BY id;

のようになります。
で、S2JDBCでこれをやろうとすると、

List<Employee> employees = jdbcManager.from(Employee.class).orderBy("id").getResultList();

のように書けばいいんですが、この時実際に発行されるSQL

select T1_.ID as C1_, T1_.NAME as C2_ from EMPLOYEE T1_ order by C1_

のようになります。
※ 例えばlog4Jを使っている場合、log4j.logger.org.seasar.extension.jdbc=DEBUGといった感じで設定されていれば表示されます。

何がしたいか

特定のデータを優先的に一覧の先頭に持って来たいという場合があるとします。
例えば、自分のIDを持っているデータ。
この時、下記のようにすればID=2のデータを先頭に持ってくることが可能です。

SELECT * FROM employee ORDER BY id <> 2, id;

※ すべてのRDBMSで実行可能かどうかは調べてないです。。。
※ 「<>」を使ったのは、bool型の場合false->trueの順番でソートされるためです

これを単純にS2JDBCでやろうとすると、

List<Employee> employees = jdbcManager.from(Employee.class).orderBy("id <> 2, id").getResultList();

のようになりますが、これで実行するとエラーになります。
どうやら実際に発行されるSQLのorder by句でカラムのエイリアスを使った式がダメなようです。(C1_ <> 2の部分)

select T1_.ID as C1_, T1_.NAME as C2_ from EMPLOYEE T1_ order by C1_ <> 2, C1_

で、これはPostgreSQLの話で、例えばMySQLだとこのSQLを正しく実行できます。

create table employee (
  id INTEGER auto_increment PRIMARY KEY, 
  name VARCHAR(128) NOT NULL
);
insert into employee (name) values ('hogeo'),('hogetaro');
select T1_.ID as C1_, T1_.NAME as C2_ from EMPLOYEE T1_ order by C1_ <> 2, C1_;

解決策

上の話からすると、MySQL使えばいいってことなんですが、
PostgreSQLから離れられないってこともありますのでちょっと手を加えてみました。
このあたりの制御をしているのが、org.seasar.extension.jdbc.query.AutoSelectImplクラスなんで直接ソースコードを変えてもいいんですが今回はこのクラスを拡張する形でやってみました。
どこを変えたいかというと、prepareOrderBy()メソッドです。

    protected void prepareOrderBy() {
        if (StringUtil.isEmpty(orderBy)) {
            return;
        }
        orderByClause.addSql(convertCriteria(orderBy, true));
    }

ここで、convertCriteriaメソッドを呼んでいますが、これは第1引数の文字列内で指定されたプロパティ名をT1_.idだとかC1_だとかに置換してくれるメソッドです。第2引数がtrueの場合はC1_のような形(カラムのエイリアス名)に、falseの場合はT1_.idのような形(テーブルのエイリアス名)に置換します。
なので、今回の目的としてはtrueではなくfalseを指定できればよいということです。

package kamegu.common.jdbc;

import org.seasar.extension.jdbc.manager.JdbcManagerImplementor;
import org.seasar.extension.jdbc.query.AutoSelectImpl;
import org.seasar.framework.util.StringUtil;

public class MyAutoSelectImpl<T> extends AutoSelectImpl<T> {

	public MyAutoSelectImpl(JdbcManagerImplementor jdbcManager, Class<T> baseClass) {
		super(jdbcManager, baseClass);
	}

	@Override
	protected void prepareOrderBy() {
        if (StringUtil.isEmpty(orderBy)) {
            return;
        }
        orderByClause.addSql(convertCriteria(orderBy, false)); //true -> false
	}
}

AutoSelect自体はSeasar2コンポーネントとして登録されているわけではないため、MyAutoSelectImplに置き換えるためにはそれを呼び出しているJdbcManager(コンポーネント登録されています)から作り直す必要がありました。

package kamegu.common.jdbc;

import org.seasar.extension.jdbc.AutoSelect;
import org.seasar.extension.jdbc.manager.JdbcManagerImpl;

public class MyJdbcManagerImpl extends JdbcManagerImpl {

	public <T> AutoSelect<T> from(Class<T> baseClass) {
        return new MyAutoSelectImpl<T>(this, baseClass).maxRows(maxRows)
                .fetchSize(fetchSize).queryTimeout(queryTimeout);
    }
}

必要なクラスは作成したので、後は設定です。
s2jdbc.diconを1行書き換えます。

    <component name="jdbcManager" class="kamegu.common.jdbc.MyJdbcManagerImpl">

log4Jを使っている場合は、デバッグ情報としてSQLを表示されるためにlog4j.propertiesに1行追加します。

log4j.logger.kamegu.common.jdbc=DEBUG

これで実行すると発行されるSQLは下記のようになり、正しく取得できました。

select T1_.ID as C1_, T1_.NAME as C2_ from EMPLOYEE T1_ order by T1_.ID <> 2, T1_.ID