概要
Domaは、S2Daoのスタイル(DAOパターンや2 Way SQL)を踏襲したJava6(JDBC4.0)対応のO/Rマッパーです。 S2Daoの良いアイデアや機能は受け継ぎつつも、扱いにくかったりプログラミングミスにつながりやすかったりする点については積極的に改善を試みています。 また、aptを利用することで、S2Daoでは実現できなかった新しい機能も提供しています。
Domaは、S2Daoの次のアイデアや機能を受け継いでいます。
一方、Domaは、S2Daoの次の点を改善しています。
- Seasar2への依存
- AOPの利用
- 命名規約
- JavaとSQLの分離
- SQLコメントの文法
- JDBCを直接利用する方法
- DaoメソッドとSQLファイルのマッピング
- DaoメソッドのパラメータとSQLファイル上のバインド変数コメントのマッピング
- Daoの初期化コスト
さらに、Domaは、新しく次のような機能を提供します。
S2Daoから受け継いだアイデアや機能
DAOパターン
DomaではS2Daoと同様にDAOパターンを採用しています。
DAOパターンを採用するのは、次のような利点があるからです。
- データアクセスの箇所を特定しやすい
- データアクセス処理をカプセル化できる(SQLの自動生成処理をJDBCを直接利用した処理に変更しても利用側プログラムに影響を与えない)
- テストをしやすい(モックを作成しやすい)
2 Way SQL
S2Daoでは、外部ファイルに記述したSQLを、Javaとマッピングした形でフレームワークの一部として取り込むことも、単独でSQLツール上で実行することも可能としています(2 Way SQL)。
Domaでは、このアイデアをそのまま採用しています。ただし、マッピングルールやSQLコメントの用い方については変更を加えています。
2 Way SQLは、フレームワークで扱うSQLを単独でテストしたり、Java開発者とSQL作成者の作業を分離したりするのに非常に有効な方法です。
複数RDBMS対応
S2Daoでは、SQLファイル名にRDBMS名を含めることで特定のRDBMS専用のSQLを用意できました。 例えば、「EmployeeDao_selectByName.sql」という名前のSQLファイルと「EmployeeDao_selectByName_oracle.sql」という名前のSQLファイルが存在する場合、 拡張子の直前にアンダースコア区切りで「oracle」という名前が記述されている後者のファイルがOracle Database専用のSQLファイルになります。 このファイルはOracle Databaseに接続している場合にのみ使用され、それ以外のRDBMSでは「EmployeeDao_selectByName.sql」が使用されます。
Domaでも、このアイデアをほぼそのまま採用しています。 ただし、SQLファイルにRDBMS名を含めるルールをアンダースコアではなくハイフンに変更しています。 例えば、Oracle Database専用のSQLファイルは「selectByName-oracle.sql」という名前になります。
RDBMSごとにSQLを切り替える手法は、フレームワークで吸収するのが困難なRDBMS独自のSQLを活用したい場合に役立ちます。
S2Daoからの改善点
Seasar2への依存
S2Daoを利用するには、Seasar2のライブラリが必須でした。
一方、Domaでは、Seasar2への依存が一切ありません。このため、Spring FrameworkやGuiceといったSeasar2以外のDIコンテナと組み合わせやすくなっています。 また、単独で利用することも可能です。 Domaはdoma-x.x.x.jarというたった1つのjarファイルのみで動作します。
AOPの利用
S2Daoでは、実行時にDaoのインタフェースにAOPを適用することで動作していました。
一方、Domaでは、コンパイル時にaptでインタフェースから実装クラスをソースコードとして生成し、実行時には生成されたソースコードに対応する実装クラスを使用します。
AOPは便利な機能ですが、挙動が把握しにくかったりデバッグがしにくかったりといった問題点がありました。 Domaではこれらの問題点を避けるため、AOPではなくaptによるコード生成を利用しています。
ただし、S2DaoであれDomaであれ、アプリケーション開発者が作成するのはインタフェースのみという点は同じです。
命名規約
S2Daoでは、たとえば、Daoのメソッド名が「update」で始まる場合そのメソッドはUPDATE文を発行するメソッドである、といった命名規約を持っていました。
int updateEmployee(Employee employee);
一方、Domaでは、暗黙的な命名規約ではなく、アノテーションを採用しています。 たとえば、UPDATE文を発行するメソッドには@Updateを注釈します。 メソッド名に制約はありません。 Domaでは、アノテーションを利用することで意図をより明確にプログラムコード上に表現し、挙動をわかりやすくしています。
@Update int save(Employee employee);
JavaとSQLの分離
S2Daoでは、DaoのメソッドにアノテーションでSQL全体やSQLの一部を記述することを認めていました。
Domaでは、DaoのメソッドにアノテーションでSQLの全体や一部を記述することを認めません。 JavaのコードにSQLを記述することは、アプリケーションの保守性を損ねるからです。 SQLの記述は、すべて外部ファイルで行うことになっています。
SQLコメントの文法
S2Daoでは、SQLに含まれたSQLコメントの前後にスペースがあるかどうかの違いでエラーが発生するなど非常に細かな間違いやすい文法を持っていました。 また、間違いが含まれていた場合のエラーメッセージもわかりにくいものでした。
Domaでは、文法を見直し厳密にしました。また、aptで検証することで、間違いがあっても「コンパイル」時にエラーを検出し、わかりやすいメッセージを表示するようにしています。 実行しなくてもエラーの有無を確認できるのは、S2Daoに比べて大きな利点です。
JDBCを直接利用する方法
S2Daoでは、Daoの特定のメソッドでJDBCを直接利用するには、インタフェースの実装クラスを作成しメソッドをオーバーライドしなければいけませんでした。 クラス名を設定ファイル等に記述している場合、この方法では、設定を変更する必要がありました。
@S2Dao(bean = Employee.class) public interface EmployeeDao { Employee selectByName(String name); }
public abstract class AbstractEmployeeDao implements EmployeeDao { @Override public List<Employee> selectByName(String name) { ... } }
Domaでは、@Delegateというアノテーションを使って、JDBCを直接利用する処理を別クラスに委譲できます。
@Dao(config = AppConfig.class) public interface EmployeeDao { @Delegate(to = EmployeeDaoDelegate.class) List<Employee> selectByName(String name); }
public class EmployeeDaoDelegate { ... public List<Employee> selectByName(String name) { .... } }
この方法は、クラス階層に変更を加えないためより柔軟です。 たとえ、クラス名が設定ファイルに記述されていても変更を加える必要がありません。
詳細は、デリゲート定義を参照してください。
DaoメソッドとSQLファイルのマッピング
S2Daoでは、SQLファイルをDaoと同じパッケージに配置しなければいけませんでした。 この仕様では、Daoが同じパッケージに複数存在する場合に、ひとつのパッケージ内に存在するSQLファイルが多くなりすぎて管理が難しくなります。
Domaでは、Daoごとに異なるディレクトリでSQLファイルを管理する仕様になっています。 このため、SQLファイルをDaoごとに管理しやすくなっています。 また、DaoメソッドとSQLファイルを相互に遷移するEclipseプラグインを用意しているので、これを使うとさらに開発/管理がしやすくなります。
詳細は、SQLファイルを参照してください。
DaoメソッドのパラメータとSQLファイル上のバインド変数コメントのマッピング
S2Daoでは、メソッドのパラメータがSQLファイル内のバインド変数コメントで参照できるようにArgumentsアノテーションを使用してパラメータごとに名前をつける必要がありました。
@Arguments( { "job", "salary" }) List<Employee> selectEmployeeByJobDeptno(String job, BigDecimal salary);
Domaでは、Argumentsアノテーションに相当するものは不要です。aptによりメソッドのパラメータ名をそのまま使用できるからです。
@Select List<Employee> selectEmployeeByJobDeptno(String job, BigDecimal salary);
Domaの新しい機能
コンパイル時のコード生成
aptを使い、コンパイル時にコードを生成できます。 たとえば、次のインタフェースをコンパイルすると、インタフェースの実装クラスが自動生成されます。
@Dao(config = AppConfig.class) public interface EmployeeDao { @Select List<Employee> selectByName(String name); }
コンパイル時のコード生成には次のような利点があります。
- 実行時に必要なメタ情報をソースコードで記述できるので、リフレクションを使ってメタ情報を構築するフレームワークに比べてパフォーマンスがよい
- ソースコードに記述された通りに動作するので、コンパイル時や実行時にバイトコードエンハンスが行われるコードよりも見通しがよい
- ブレークポイントを設定したりステップ実行をしたりとデバッグがしやすい
コンパイル時の規約チェック
aptを使い、コンパイル時にコードがフレームワークの規約を満たしているかチェックできます。
たとえば、次のコードをコンパイルすると、update
メソッドの部分にエラー表示がされます。
なぜならば、@Update
を注釈したメソッドは更新件数を表すint
型の値を返さなければいけないというルールがあるからです。
エラーは、Eclipseを使っている場合にはエディタに表示され、Antなどでjavacを実行した場合にはAntを実行したコンソールに表示されます。
@Dao(config = AppConfig.class) public interface EmployeeDao { @Update void update(Employee employee); }
コンパイル時に規約をチェックすることで、わざわざアプリケーションを実行しなくてもエラーに気づくことができます。
コンパイル時のSQLファイル存在チェックとSQLコメントの文法チェック
aptを使い、コンパイル時にDaoメソッドに対応するSQLファイルが実際に存在しているかチェックできます。
たとえば、次のコードをコンパイルすると、selectById
メソッドに対応するSQLファイルが存在するかどうかクラスパスの検索が行われます。
@Dao(config = AppConfig.class) public interface EmployeeDao { @Select Employee selectById(Integer id); }
SQLファイルが存在しない場合は、その時点でselectById
メソッドの部分にエラーが表示されます。
SQLファイルが存在する場合は、続いてSQLコメントの文法チェックが行われます。
たとえば、selectById
メソッドに対応するSQLファイルが次のようなSQLの場合、エラーが発生します。
なぜならば、メソッドではパラメータ名が「id」であるのに、SQLコメントでは「employeeId」と記述されているからです。
select * from employee where id = /*employeeId*/0
コンパイル時にSQLファイルの存在チェックやSQLコメントの文法チェックを行うことで、必ず発生する実行時エラーをコンパイル時に検出できます。
アプリケーション固有の値型への対応
アプリケーション固有の値型を作成し、そのクラスをデータベースのテーブルのカラムに対応づけることができます。
Domaではそのようなクラスを基本型と区別してドメインクラスと呼んでいます。
たとえば、電話番号を扱うアプリケーションであれば、PhoneNumber
というドメインクラスが作成できるでしょう。
@Domain(valueType = String.class) public class PhoneNumber { private final String value; public PhoneNumber(String value) { this.value = value; } public String getValue() { return value; } public String getAreaCode() { //アプリケーションに固有の振る舞い ... } }
ドメインクラスには次のような利点があります。
- データベース上のカラムの型が同じあってもアプリケーション上意味が異なるものを別のJavaの型で表現できる
- 概念をクラスとして明確に表現することでプログラミングをわかりやすくできる
- 値と振る舞いを同じクラスで表現できる