この文書は、今後 Doma 2 を開発していくにあたり、新規追加する機能について作者の考えを述べたものです。
- Doma は Annotation Processing Tool (apt) を利用した Java の Dao フレームワークである
- Doma 1 を Java 6 で開発してきたが、Java 8 への対応に伴い Doma 2 へメジャーバージョンアップする
- サポートする Java のバージョンは 8 以上とする
- Java 8 ならではの機能を追加するとともに、既存機能の拡張も行う
- Doma 1 との互換性はできるだけ保つが 100 %は目指さない (互換性がない箇所については移行方法を示す)
- Doma 1 の開発から掲げてきたコンセプトは変えない
- 暗黙的な規約よりも明示的な設定を重視する
- 他のライブラリに依存しない
- できる限り実行時ではなくコンパイル時にエラーを検出する
- できる限りキャッシュしない
- わかりやすいエラーメッセージを出力する)
java.util.Optional
を以下の箇所で利用可能とします。
- エンティティのプロパティ
- Dao メソッドのパラメータや戻り値
@Dao(config = AppConfig.class)
public interface EmpDao {
@Select
Optional<Emp> selectByName(String name);
}
DataSourceを直接使う処理などを行う場合、Doma 1 では @Delegate
アノテーションを指定することで、別クラスに処理を委譲していました。
Java 8 では、default キーワードを使うことでインタフェースに実装を持てるようになったのでこちらを採用します。
また、Doma 2 では Config
を取得する手段を新しく提供します。
@Dao(config = AppConfig.class)
public interface EmpDao {
default Optional<Emp> selectByName(Optional<String> name) {
// Dao に定義された Config の インスタンスを取得
Config config = Config.get(this);
DataSource dataSource = config.getDataSource();
...
}
}
IterationCallback に関してラムダ式を使った簡潔な記述をサポートします。
@Dao(config = AppConfig.class)
public interface EmpDao {
@Select(iterate = true)
Salary iterate(IterationCallback<Salary, Emp> callback);
}
たとえば、上記の定義に対し次のような記述ができるようなヘルパーメソッドを提供します。
Salary sum = dao.iterate(Iter.reduce(Salary.ZERO, (acc, emp) -> acc.add(emp.salary)));
org.seasar.doma.jdbc.JdbcLogger
や org.seasar.doma.jdbc.SqlFileRepository
の実装は任意にカスタマイズできますが、これらの実装クラスに処理中の Dao メソッドに対応する java.lang.reflect.Method
インスタンスをパラメータとして渡すことにします。
これにより、たとえばメソッドに注釈されたアノテーションにより条件分岐するといったことが可能になります。
Doma により自動生成される Dao メソッドの実装コードは、主に org.seasar.doma.internal.jdbc.query.Query
や org.seasar.doma.internal.jdbc.command.Command
と呼ばれる2つの internal なコンポーネントを利用しています。
Doma 2 ではこれらのコンポーネントを public な API とし、Config で実装クラスを差し替えられるようにします。これにより、SQL の生成処理や JDBC の API へのアクセス方法などをカスタマイズ可能になります。
具体的な対応は現時点では考えていません。理由は以下の通りです。
- JDBC ドライバごとに対応がまちまちになるのでは?という懸念がある
- 日付に関する処理はドメインクラスでラッピングして使ったほうが扱いやすいことを考えると JDBC で JSR 310 を直接扱う必要性が低いと思われる
- java.sql.Date/Time/Timestamp や java.util.Date にマッピングしたドメインクラスの中で適宜変換をかければよい
次のように記述して実現する案をいただきました。
@Dao(config = AppConfig.class)
public interface EmpDao {
@Select(lazy = true)
Stream<Emp> selectAll();
}
しかし、次の懸念点があるため現時点では対応に消極的です。
- 機能的には @Select(iterate = true) と重複する
- Stream は AutoCloseable であるが、try で囲まないと close 忘れが発生する
- トランザクション境界の外でアクセスされる可能性がある
- 遅延ローディングは行わないという Doma のポリシーに反する
たとえば、Order というテーブルを作った場合、SQL の中ではダブルクォートで囲むなどの処理が必要です。
delete from "Order" where id = 1
このような SQL を生成するためのオプションを用意します。
@Table
などのアノテーションに新たな要素を追加してダブルクォートで囲むことを示せるようにします。
@Entity
@Table(enclose = true)
public class Order {
...
}
Doma 1 では 9 つのオプションを提供していますが、Doma のオプションとわかるような名前空間がないのでわかりにくくなっています。整理します。
IterationCallback<ENTITY, RESULT> を廃止して代わりに Function <Stream, RESULT> を受け取れるようにするのは如何でしょうか?
そうすれば Stream の豊富な機能も使えますし、Collectorなど色々使いまわせるようになります。
java.util.stream.Stream で懸念されているような
といった問題もある程度解消できるのかな、と思います。
// もちろん Function<Stream, Stream> みたいなのも渡せちゃうので完全に防げはしないですが……。