データアクセス層は、ドメインロジックにおけるデータアクセスロジックの API とその実装を提供します。データベースの
読み書きとドメインオブジェクトへのマッピングが主な役割です。データアクセス層はドメイン層の一部であり、ドメイン層
以外のクラスが直接データアクセス層を参照することはありません。データアクセス層では、インターフェースとその実装
を DAO パターン によって分離しています。インターフェースは org.unitedfront2.dao
パッケージに、その実装クラスはそれ以下のパッケージに収録されています。現在 United Front 2 で対応してい
るのは MySQL
に対する Spring JDBC 実装です。
ドメイン層とデータアクセス層はインターフェースを通して完全に分離されており、かつパッケージとしても分離しています が、両者は密接に結びついています。実際のところ、データアクセス層とドメイン層のパッケージは循環依存関係にあり ます。厳密にはデータアクセス層とドメイン層のクラスは同一パッケージに配置されるべきですが、それではそのパッ ケージの債務が増えてしまうというデメリットがあるため、ここではディレクトリを分ける程度の意味合いでパッケージを 使っています。本質的には、データアクセス層はドメイン層の一部分と考えてください。
データアクセスインターフェースは、データへアクセスするために必要となる API を定義しています。基本的に、データ アクセスインターフェースはデータベーステーブルと1対1に対応しています。データアクセスインターフェースには回復可 能な例外(チェックされる例外)は定義されません。データの整合性違反などを検知した際に発生するのは実行時例外 になります。
データアクセス API はドメイン層のクラスが持っているようなドメイン(問題対象領域)を意識した高レベル API では
なく、データを読み書きすることに意識を置いた低レベルな API です。SQL のように扱える API が適切です。
登録( register* )、取得( find* )、更新( update* )、
削除( delete* )以外のメソッド名は、粒度の点で問題がないか十分検討する必要があります。
オブジェクトのライフサイクルを次に示します。

DAO パターンの目的は、ビジネスロジックとデータアクセス実装をインターフェースによって明確に分離することです。こ れを達成するためには、DAO インターフェースの仕様を正しく記述し、その単体テストを正しく実装する必要があります。 正しい仕様記述と単体テストは、データアクセス実装に関係なく再利用できます。例えば、データアクセス実装がリレー ショナルデータベースではなくファイルシステムに変更されてもそれらは利用できるはずです。有名なデータアクセステ ストフレームワークとして DbUnit が挙げられますが、その名 前が示すとおりデータアクセス実装に依存した単体テストになってしまうため、ここでの採用は適切ではありません。こ こでいう仕様記述と単体テストはもっと抽象度の高いものである必要があります。United Front 2 における具体 的な仕様記述と単体テスト方式は、契約による設計 の項で述べています。
データアクセス層から回復可能な例外処理を排除することで、データアクセス層のコードはかなりシンプルになります。 これは、肥大化しがちなデータアクセス層を薄くするための工夫の一つです。代わりに整合性を確認するための API を提供することで、ドメイン層にデータの整合性と例外処理の責任を委譲しています。データアクセス層の API は、 データの整合性が違反しないように呼び出されることを前提としています。
一般的なデータアクセスインターフェースは次のようなメソッドを持ちます。
| メソッド名 | 説明 |
|---|---|
register |
ドメインオブジェクトを登録します。ドメインオブジェクトの永続プロパティがデータベースと同期します。主キーなど、データ
ベース側で自動生成される値は、引数のドメインオブジェクトにその値が設定されます。このメソッドを実装するクラスは
インターフェース
Registerable
を実装します。
|
find |
主キーの値を引数として渡し、ドメインオブジェクトを取得します。ドメインオブジェクトの永続プロパティはデータベースと
同期しています。もし対応するドメインオブジェクトが見つからなかった場合、null を返します。この
メソッドを実装するクラスの主キーが一つの整数型である場合、インターフェース
SimpleFindable
を実装します。
|
update |
ドメインオブジェクトを更新します。ドメインオブジェクトの永続プロパティがデータベースと同期します。データベース側で
自動生成される値は、引数のドメインオブジェクトにその値が設定されます。このメソッドを実装するクラスはインター
フェース
Updatable
を実装します。
|
delete |
指定した主キーを持つデータを削除します。削除対象となるドメインオブジェクトのプロパティとなるドメインオブジェクトも
極力 削除されます。プロパティとなるドメインオブジェクトが他のドメインオブジェクトからも参
照されている場合などは削除されない可能性があります。このメソッドを実装するクラスの主キーが一つの整数型であ
る場合、インターフェース SimpleDeletable
を実装します。
|
上記の基本メソッドに加え、次のようなメソッドを持つことがあります。基本的に ColumnName はドメインモ デルのプロパティに対応します。
| メソッド名 | 説明 |
|---|---|
findColumnName
|
主キーの値を引数として渡し、ドメインオブジェクトのプロパティ columnName の値を取得します。 |
findByColumnName
|
任意のプロパティ columnName
の値を引数として渡し、その値を持った、完全なドメインオブジェクトまたは
そのリストを取得します。もし対応するドメインオブジェクトが見つからなかった場合、null または
空のリストを返します。
|
findByColumnName
Like |
任意のプロパティ columnName
の値の一部を引数として渡し、その値を一部に持った、完全なドメインオ
ブジェクトまたはそのリストを取得します。もし対応するドメインオブジェクトが見つからなかった場合、
null または空のリストをします。
|
findCondition
|
条件 Condition
を完成させるために必要なプロパティの値を引数として渡し、条件に合った完全なドメイ
ンオブジェクトまたはそのリストを取得します。もし対応するドメインオブジェクトが見つからなかった場合、
null または空のリストを返します。
|
count |
永続化されているドメインオブジェクトの総数を返します。 |
updateColumnName
|
ドメインオブジェクトの一つのプロパティ columnName を更新します。 |
自動生成の対象となるデータは、主キーなど、最小限のものに限定します。日付やコードなどをデータベース側で自動
生成してしまうと、API の仕様が複雑化し、柔軟性が低下します。日付やコードはドメインモデルかテーブルモジュー
ルで設定し、それをデータアクセスオブジェクトへ渡す方式が好ましいでしょう。日付の生成は全てのデータアクセスオブ
ジェクトで利用できますし、日付データ処理専用の DateDao
を使うこともできます。コードの生成は Apache Commons Lang の RandomStringUtils
を利用できます。コードサンプルとしては、Mail のソースコード
を参照ください。
サンプルコード SampleDomainDao を示します。完全なコードは
SampleDomainDao
のソースコード
を参照してください。
public interface SampleDomainDao extends Registerable<SampleDomain>,
SimpleFindable<SampleDomain>, Updatable<SampleDomain>, SimpleDeletable,
Dao {
SampleDomain findByCode(String code);
int count();
}
Registerable 、Updatable 、
SimpleFindable 、Deletable を実装することは一見冗長な作業のよう
に思えるかもしれません。しかしこれは設計に柔軟性を与えること以上に、仕様の再利用を促進する効果があります。
JavaDoc には、上位のクラスまたはインターフェースからドキュメントを継承する仕組みがあります。仕様に追記事項
があれば、上位のメソッドをオーバーライドし、追記分の仕様のみを記述します。
JDBC 実装には Spring の JDBC 抽象フレームワークを利用しています。JDBC 実装はアノテーションベースの Bean 定義を採用しています。詳細は Spring のドキュメント Data access using JDBC を参照してください。
JDBC と O/R マッピング製品のどちらを選ぶべきでしょうか?JDBC の利点は、データベースを操作する際に最も効 率的で標準的な SQL を利用できる点です。反面、Java オブジェクトとリレーショナルデータベースの性質の差(イン ピーダンスミスマッチ)を埋めるためのマッピングロジックを実装する必要があります。O/R マッピング製品の主要な利 点はこのマッピングロジックをフレームワークが担当する点にありますが、SQL で実現できるの全てのケースに対応し ているわけではなく、場合によっては実装がより複雑になることもあります。United Front 2 はデータ処理が複雑 になると想定されるため、軽量で扱いやすい JDBC 実装を採用しました。単純な SQL の発行とマッピングに対して は、簡単なフレームワークも用意しています。
DaoSupport
は全ての JDBC 実装を支援する抽象クラスです。SimpleDaoSupport
は register 、find 、update 、
delete などの基本的なメソッドに対するデフォルトの実装を提供します。

サンプルコード SampleDomainDaoImpl を示します。完全なコードは SampleDomainDaoImpl
のソースコード
を参照してください。
// アノテーションベースの Spring Bean 定義。
@Repository(value = "sampleDomainDao")
public class SampleDomainDaoImpl extends SimpleDaoSupport<SampleDomain>
implements SampleDomainDao {
// SimpleDaoSupport の抽象メソッドの実装
@Override
protected Class<SampleDomain> getDomainClass() {
return SampleDomain.class;
}
// 以下、SampleDomainDao の実装
@Override
public SampleDomain findByCode(String code) {
try {
return getSimpleJdbcTemplate().queryForObject(
"SELECT * FROM SampleDomain WHERE Code = ?",
createRowMapper(), code);
} catch (DataAccessException e) {
if (logger.isInfoEnabled()) {
logger.info(e.getMessage());
}
return null;
}
}
@Override
public int count() {
return getSimpleJdbcTemplate().queryForInt(
"SELECT COUNT(*) FROM SampleDomain");
}
}