第3章 逆引きカタログ J2EE編
Authors:Hashimoto Masanori
私たちが作るアプリケーションのほとんどは、どこかで永続的なデータを扱うことになります。
そのデータの保存先は、リレーショナルデータベースやテキストファイル、他システムなどになるでしょう。
そして保存されたデータへのアクセスで使用するAPIは、保存先によって変わっていきます。
例えば、リレーショナルデータベースだとJDBCを使用します。
ファイルだとjava.ioパッケージあたりを使用したりします。
また、リレーショナルデータベースのみに焦点を当ててみても、ベンダやバージョンによって発行するSQL文を変えなければなりません。
ファイルに永続的なデータを保存していて、その保存先がデータベースに変更されたときのことを想像してください。
ビジネスロジック(業務ロジック)の中にデータアクセスにまつわるコードを書いている場合、保存先の変更が容易ではありません(同様のことが、データベースの変更時にもありえます)。
ビジネスロジックがデータの保存先のAPIに依存してしまっているのです。
また、弊害として、同じようなデータアクセスのロジックがいたるところあるような状態も生じるでしょう。
そのようなときはDAO (Data Access Object) パターンを使ってみましょう。
DAOパターンは、永続的なデータへのアクセスを、ビジネスロジックから抜き出すことができます。
また、インタフェースを使ってデータへのアクセス方法を抽象化しますので、ビジネスロジックではシンプルに永続的なデータを扱うことができるようになります。
DAOパターンは、データアクセスをビジネスロジックから排除し、データアクセスオブジェクト(DAO)としてカプセル化します。
データアクセスの手段や実装が変わっても、DAO自体がビジネスロジックに公開しているインタフェースは変わりませんので、ビジネスロジックの変更が発生しません。
DAOはデータ保存先の、アダプタのようなものになります(図12)。
もし、データアクセスに関わるロジックがビジネスロジックの中にあったらどうなるでしょうか?
データアクセスの手段や実装が変わると、ビジネスロジック中からデータアクセスをしている個所を見つけだし、変更していかなければなりません(図13)。
それは退屈で困難な作業になります。
DAOパターンは「データアクセス部分をまとめて整理整頓する技術」とも言えそうです。
商品の倉庫管理を具体例にあげてDAOパターンを説明します。
管理される倉庫の情報は、カテゴリ・商品名・個数・単価です。
その在庫の情報はリレーショナルデータベースに保存します。
在庫の管理者は、商品の追加・更新・削除・カテゴリごとでの検索ができます(図14)。
WarehouseLogicクラス(
リスト17)に、ビジネスロジックのすべてを記述しています。
サンプルはとても簡単なビジネスロジックしかありませんのでWarehouseLogicクラスの必要性が薄くなりますが、実際の業務アプリケーションは、このサンプルほどシンプルなものではありません。
業務アプリケーションでは、ビジネスロジックを記述するクラス(メソッド)は、とても複雑なものになります。
リスト17 WarehouseLogic.java
package daoSample.service;
@ |
import daoSample.DaoFactory;
import daoSample.Item;
import daoSample.ItemDao;
|
public class warehouseLogic {
private ItemDao dao = DaoFactory.createItemDao(); …A
public Item[] selectByCategory(String category) {
return dao.selectByCategory(category);
}
(中略)
}
リスト17-Aが本題のデータアクセスオブジェクトです。
DaoFactoryクラス(
リスト18)経由でItemDaoインタフェース(
リスト19)を実装するクラスを取得しています。
そして、各メソッドでItemDaoを使用してデータアクセスをします。
リスト17-@のように、JDBCやデータアクセスのフレームワークのAPIがインポートされていないのが、Daoを使用するクラスのポイントになります。
リスト18のDaoFactoryクラスがなければ、Doa生成ロジックがビジネスロジックに紛れ込んでしまいます。
そこでDataSourceなどのAPIがビジネスロジックに入らないように、ファクトリクラスを利用します。
ファクトリクラスにて、すぐに使えるDaoクラスを生成します。
リスト18 DaoFactory.java
public class DaoFactry {
public static ItemDao createItemDao() {
return new jdbcItemDao(getDataSource());
}
private static DataSource getDataSource() {
InitialContext initCon = null;
DataSource ds = null;
try {
@ |
initCon = new InitialContext();
ds = (DataSource)initCon.lookup(
"java:comp/env/jdbc/HSQLDB");
|
} catch (NamingException e) {
e.printStackTrace();
if (initCon != null) {
try {
initCon.close();
} catch (NamingException ex) {
ex.printStackTrace();
throw new DataAccessException();
}
}
throw new DataAccessException();
}
return ds;
}
}
このDaoFactoryでは、
リスト18-@にてJNDIを経由してDataSourceを取得して、JdbcItemDaoオブジェクトを生成しています。
また、複数の保存先に対応するようなアプリケーションの場合、アブストラクトファクトリパターンを適用すると柔軟に対応できるようになります。
たとえば、保存先をDB2、Oracle、ファイルに切り替え可能なアプリケーションでは、アブストラクトファクトリパターンを使うと便利でしょう(図15)。
ビジネスロジック(
WarehouseLogic.リスト17)の中では、DAOのインタフェース(
ItemDao.リスト19)を使ってデータアクセスするように心がけます。
すると使用するDAOの実装クラス(
JdbcItemDao.リスト20)を変更しても、ビジネスロジックの修正は不要になります。
ビジネスロジックとDAOを疎結合にしておくのです。
リスト19 ItemDao.java
public interface ItemDao {
abstract Item[] selectByCategory(String category);
(中略)
}
リスト20 JdbcItemDao.java
public class JdbcItemDao implements ItemDao {
private DataSource source;
public jdbcItemDao(DataSource source) {
this.source = source;
}
public Item[] selectByCategory(String category) {
Resultset rs = null;
PreparedStatement statement = null;
Connection conn = null;
try {
StringBuffer sql = new StringBuffer();
sql.append("select ");
sql.append(" id, ");
sql.append(" category, ");
sql.append(" name, ");
sql.append(" number, ");
sql.append(" cost, ");
sql.append("from ");
sql.append(" stock ");
sql.append("where ");
sql.append(" category=? ");
conn = source.getConnection();
statement = conn.prepareStatement(sql.toString());
int index = 1;
statement.setString(index++, category);
rs = statement.executeQuery();
List list = new ArrayList();
while(rs.next()) {
Item item = new Item();
item.setId(rs.getInt("id"));
item.setCategory(rs.getString("category"));
item.setName(rs.getString("name"));
item.setNumber(rs.getInt("number"));
item.setCost(rs.getInt("cost"));
list.add(item);
}
return (Item[]) list.toArray(new Item[list.size()]);
@ |
} catch (SQLException e) {
rollback(conn);
throw new DataAccessException(e.getMessage(), e);
|
} finally {
close(rs);
close(statement);
close(conn);
}
}
(中略)
private void commit(Connection conn) {
if (conn != null) {
try {
conn.commit();
} catch (SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
}
}
private void rollback(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e1) {
throw new DataAccessException(e1.getMessage(), e);
}
}
}
private void close(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
}
}
private void close(PreparedStatement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
throw new DataAccessException(e.getMessage, e);
}
}
}
private void close(Resultset rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
}
}
}
リスト20のJdbcItemDaoクラスが実際のDAOになります。
データアクセスに関するロジックはこのDAOクラス内に記述します。
サンプルでは、JDBCを直接使ってデータアクセスしていますが、この部分をO/RマッピングツールやJakartaDbUtilsなどを使って実装することが実際の業務では主流になってきています。
データベースアクセスでは、SQLExceptionをキャッチしなくてはならないときが多くあります。
そのキャッチしたSQLExceptionを再度throwしていると、ビジネスロジックまでSQLExceptionが投げられてしまい、ビジネスロジックにJDBCのAPIが混入してしまうことになります。
この場合、SQLExceptionをキャッチしたら、別の例外に置き換えて、投げ直さなければなりません。
JdbcItemDaoでは、
リスト20-@のようにRuntimeExceptionを継承した実行時例外である「DataAccessException」で投げ直しています。
他にも、アプリケーション例外(検査例外)を作成し、それで投げ直す方法もあります。
とにかく、SQLExceptionをビジネスロジックにまで投げてはいけません。
もし永続データの保存先がファイルの場合は、同様にIOExceptionやFileNotFoundExceptionをビジネスロジックまで投げてはいけません。
◎MockDaoを使う
分散開発で、データアクセス部分ができないとビジネスロジックが書けない「実装の順番待ち」という状態がよくあります。
そうなると、時間的にもコスト的にもあまり良いことではありません。
そのようなときにDAOパターンを適用していれば、仮のDAOクラス(ファサードパターンで解説したモックオブジェクト)を使ってビジネスロジックを実装していくことが可能です。
サンプルで説明すると、
JdbcItemDaoクラスができるまで、
WarehouseLogicクラスのテストはできません。
しかし、
JdbcItemDaoクラスは他社が実装することになっており、自社で作るわけにはいかないということが実際の業務では起こりがちです。
そういうときにMockItemDaoクラス(
リスト21)を作成するのです。
作り方は簡単、
ItemDaoクラスをimplementsして、データアクセスせずに格メソッドで仮のデータを返してあげればよいのです。
リスト21 MockItemDao.java
public class MockItemDao implements ItemDao {
public Item selectById(int id) {
Item item = new Item();
item.setId(0);
item.setCategory("食品");
item.setName("テスト缶詰");
item.setNumber(5);
item.setCost(1000);
}
(中略)
}
DAOパターンを使うと、データアクセスをとてもシンプルに行うことができます。
実際には「将来にわたって永続データの保存先が変わること」ということはなかなかありえません。
しかし、DAOパターンはビジネスロジックをシンプルにするためだけにでも有効ですし、現在スタンダードな手法となっています。
DAOパターンは、基礎に「ファサードによる層の分割」があります(
ファサードパターン参照)。
その分割された層のうちの「データアクセス層」というのが、DAOパターンなのです。
JTA(Java Transaction API)でトランザクション管理 |
実際にDAOパターンを使って一番悩むのがトランザクションではないでしょうか?
トランザクションの管理には、JTAを使うと楽になります。
JTAには、トランザクションを制御するAPIが定義されています。
詳しくは、下記URLを参照してください。
|