はじめに
別稿「iBATIS.NETにてO/Rマッピングを行う(SQL Maps編)」に引き続き、本稿ではiBATIS.NETのDAO Framework機能について解説します。iBATIS.NETに関する概要およびインストール方法については、別稿の内容を参照してください。
対象読者
.NETにて開発を行っている方、また基本的なSQLを理解している方を対象としています。
必要な環境
サンプルはVisual Studio .NET 2003で作成し、.NET Framework 1.1で動作確認をしています。
サンプルアプリケーションについて
本稿のサンプルは書籍マスタのメンテナンスを行うWindowsアプリケーションです。
「SQL Maps」と「DAO Framework」を用いて、MDB(Microsoft Accessのデータベース形式)にアクセスを行い、書籍テーブルへの追加・更新・参照・削除といったデータベース操作を実行します。このサンプルにおけるiBATIS.NET・クラス・設定ファイルの関連図を示します。
このアプリケーションはプレゼンテーション層(画面)・サービス層(ビジネスロジック)・パーシステンス層(データベースアクセス)というレイヤに分かれています。なおiBATIS.NET(DataMapper-1.2.1,DataAccess-1.6.1)がサンプルに同梱されているため、改めてiBATIS.NETを入手する必要はありません。
DAO(Data Access Object)導入のメリット
本稿ではiBATIS.NETの「DAO Framework」について紹介します。MicrosoftがADO以前に提唱していたDAOを思い浮かべる方も多いと思いますが、iBATISのDAOはデータベースへのアクセス処理を行う「Data Access Object」パターンを意味し、以下のような問題を解決することができます。
- サービス層とパーシステンス層の間の結合度を最小限にする。
- 外部リソースに対するアクセスを1ヵ所にまとめる。
- サービス層におけるプログラムの複雑さを最小限にする。
サービス層とパーシステンス層の間の結合度を最小限にする。
パーシステンス層の中における実装クラスや例外クラスを隠すことが可能になります。例えばデータベースアクセスにADOを使用していれば「DataReader
」「DataAdapter
」「Connection
」「Command
」といったクラスを、SqlMapsを使用していれば「Mapper
」「SqlMapper
」といったクラスをサービス層に対して隠蔽します。そのためパーシステンス層内部の変更がサービス層へ与える影響が少なくなります。
外部リソースに対するアクセスを1ヵ所にまとめる。
データベースのような外部リソースへのアクセスロジックがDAOに集中するため、OracleからSQL Serverへの移植といったRDBMSの変更、LDAPやWebServiceといった他のリソースの追加の場合にも修正箇所を局所化することができます。
サービス層におけるプログラムの複雑さを最小限にする。
「DaoManager
」クラスがファサード(サブシステムの共通APIを提供するパターン)となり、トランザクションの制御メソッドなどを提供します。サービス層からは、この「DaoManager
」と「各DAOインターフェース
」にアクセスするだけで良いため、プログラムがシンプルになります。
このような品質や保守性の向上がはかれるため、中規模から大規模の開発の機会があれば「DAO Framework」の導入を検討してみると良いでしょう。
「DAO Framework」の実装
それでは、「Book
」クラスを例にして、パーシステンス層を実装する手順を解説します。
- 「
IBookDAO
」というインターフェースクラスを作成。 - 「
BaseSqlMapDao
」というDAOのスーパークラスを作成。 - 「
BookSqlMapDao
」というDAOの実装クラスを作成。 - 管理ファイル「dao.config」に「
IBookDAO
」と「BookSqlMapDao
」を登録。
DAOのインターフェース「IBookDAO」(パーシステンス層)
最初に、インターフェース「IBookDAO
」を定義します。このインターフェースではInsertBook
やGetBook
といったサービス層に公開するメソッドの定義を宣言します。
using System; using IBatisSample.Domain.Books; namespace IBatisSample.Persistence.Interface.Books { public interface IBookDao { //登録処理の定義 void InsertBook(Book book); //取得処理の定義 Book GetBook(string bookIsbn); (中略) } }
サービス層から利用できるメソッドをインターフェースとして定義するのは、パーシステンス層とサービス層の結合度を下げるためです。インターフェースと会話することで依存関係が下がり「メンテナンス性の向上」「テストの容易性」といった品質の向上が図れるようになります。
DAOのスーパークラス「BaseSqlMapDao」(パーシステンス層)
DAOの共通処理はスーパークラス「BaseSqlMapDao
」に実装しています。
namespace IBatisSample.Persistence { public class BaseSqlMapDao : IDao { //SqlMapperの取得 protected SqlMapper GetLocalSqlMap() { DaoManager daoManager = DaoManager.GetInstance(this); SqlMapDaoSession sqlMapDaoSession = (SqlMapDaoSession)daoManager.LocalDaoSession; return sqlMapDaoSession.SqlMap; } //Insertの実行 protected object ExecuteInsert( string statementName, object parameterObject) { SqlMapper sqlMap = GetLocalSqlMap(); try { return sqlMap.Insert(statementName, parameterObject); } catch (Exception e) { throw new IBatisNetException("INSERT実行エラー '" +statementName+"' 原因: " + e.Message, e); } } //SelectObject(1件)の実行 protected object ExecuteQueryForObject( string statementName, object parameterObject) { SqlMapper sqlMap = GetLocalSqlMap(); try { return sqlMap.QueryForObject(statementName, parameterObject); } catch (Exception e) { throw new IBatisNetException("SELECT実行エラー '" +statementName+"' 原因: " + e.Message, e); } } (中略) } }
このクラスでは、
- セッションに応じた
SqlMapper
(別稿にて紹介)を取得。 SqlMapper
を用いてINSERT
やSELECT
の処理を実行。- DBアクセス時に発生しうる様々な例外処理を
IBatisNetException
というDAOの共通例外としてラップ。
といったDAOの共通処理を記述しています。(このクラスは必要に応じて拡張すると良いでしょう。)
DAOの実装クラス「BookSqlMapDao」(パーシステンス層)
「IBookDao
」インターフェースと「BaseSqlMapDao
」クラスを継承して「BookSqlMapDao
」クラスを作成します。「IBookDao
」インターフェースにて定義したメソッドの実装を行う必要があります。
using System; using IBatisSample.Domain.Books; using IBatisSample.Persistence; using IBatisSample.Persistence.Interface.Books; namespace IBatisSample.Persistence.MapperDao.Books { public class BookSqlMapDao : BaseSqlMapDao, IBookDao { (中略) //書籍の登録 public void InsertBook(Book book) { ExecuteInsert("InsertBook", book); } //書籍の取得 public Book GetBook(int bookId) { //型の変換 Book book = new Book(); book.Isbn = bookIsbn; return (ExecuteQueryForObject("GetBookById",book) as Book); } (中略) } }
それぞれのメソッドからスーパークラス「BaseSqlMapDao
」で定義したSQL実行コマンドを呼び出しています。「BaseSqlMapDao
」を継承しているため、「SqlMapper
」クラスの取得や例外の制御といった処理を記述する必要はありません。
管理ファイル「dao.config」の設定(パーシステンス層)
「DAO Framework」の設定ファイルである「dao.config
」の記述を行います。
- 接続先データベースの設定
- データベースへのアクセス方法の設定(ADO.NET/SQL Maps/NHibernate)
- DAOのインタフェースと実装クラスの紐付け
といった設定を行います。
接続先データベースの設定
iBATISにて接続するデータベースの接続文字列を記述します。
<properties resource="database.config"/> <!-- DBの接続先 --> <database> <provider name="OleDb1.1"/> <dataSource name="Access" connectionString= "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=${datasource}"/> </database>
他のプロバイダにて接続したい場合には、OleDb1.1の設定をコメントし、SQL Serverなどのコメントを外して設定を有効にすると良いでしょう。なお「database.config」というプロパティファイルには「<add key="datasource" value="iBatisSample.mdb" />
」と記述しているため${datasource}
には「iBatisSample.mdb」がセットされます。
データベースへのアクセス方法の設定
DBアクセスに使用するプロダクトを記述します。
<!-- セッションハンドラの登録 --> <daoSessionHandler id="SqlMap"> <property name="resource" value="SqlMap.config"/> </daoSessionHandler>
「DAO Framework」は「ADO.NET」「SQL Maps」「NHibernate」の3つのDBアクセス方式をサポートしています。「SQL Maps」を使用する場合は、上記の記述で問題ありません。
DAOのインタフェースと実装クラスの紐付け
DAOのインタフェースと実装クラスの紐付けの設定を行います。「interface
」にインターフェース名を、「implementation
」に実装クラス名を記述します。
<daoFactory> <!-- DAOをここに追加していきます --> <dao interface="IBatisSample.Persistence.Interface.Books. IBookDao,iBatisSample" implementation="IBatisSample.Persistence.MapperDao.Books. BookSqlMapDao,iBatisSample"/> <dao interface="IBatisSample.Persistence.Interface.Hoge. IHoge,IBatisSample" implementation="IBatisSample.Persistence.MapperDao.Hoge. Hoge,IBatisSample"/> </daoFactory>
interface
とimplementation
のペアは「完全修飾クラス名,アセンブリ名」の形式にて記述します。以前のインターフェースプログラムでは、Factoryパターンによって実装クラスをハードコーディングする必要がありましたが、最近はDI(Dependecy Injection)によって、設定ファイルの記述から実装クラスを設定することが一般的になりました。「DAO Framework」は、このような簡易のDI機能をサポートしているため、IBookDao
インターフェースにBookSqlMapDao
クラスを簡単に設定することができます。
ここまでの設定で、パーシステント層の設定は完了です。
「BookService」クラスの実装(サービス層)
最後に、DAOを利用するサービス層のプログラムを行います。
using System; using IBatisNet.DataAccess; using IBatisSample.Persistence.Interface.Books; using IBatisSample.Domain.Books; namespace IBatisSample.Service { public class BookService { private static BookService _instance = new BookService(); private DaoManager _daoManager = null; private IBookDao _bookDao = null; //コンストラクタ public BookService() { //DaoManagerの取得 _daoManager = ServiceConfig.GetInstance().DaoManager; //DaoManagerからBook用のDaoを取得 _bookDao= _daoManager.GetDao(typeof(IBookDao)) as IBookDao; } // BookServiceクラスのインスタンスを取得 public static BookService GetInstance() { return _instance; } //書籍の取得 public Book GetBook(int bookId) { Book book = null; _daoManager.OpenConnection(); book = _bookDao.GetBook(bookId); _daoManager.CloseConnection(); return book; } //書籍の登録 public void InsertBook(Book book) { _daoManager.BeginTransaction(); try { _bookDao.InsertBook(book); _daoManager.CommitTransaction(); } catch { _daoManager.RollBackTransaction(); throw; } } (中略) } }
サービス層である「BookService
」クラスは、多数のクライアントの実行に耐えられるようにSingleton(負荷がかからないようにインスタンスを1つしかつくらないように制御するパターン)にて実装しています。また、「DaoManager
」クラスは「ServiceConfig
」というサポートクラスから取得しています。
このサンプルプログラムの
_daoManager = ServiceConfig.GetInstance().DaoManager; _bookDao= _daoManager.GetDao(typeof(IBookDao)) as IBookDao; _daoManager.BeginTransaction(); _bookDao.InsertBook(book); _daoManager.CommitTransaction();
という部分から、DAO Frameworkに沿った実装を行うことで
- ファサードである「
DaoManager
」クラスから「DAOの取得」と「トランザクションの管理」が可能。 - DAOである「
IBookDao
」のメソッドにて検索や更新処理の実施が可能。
という点が理解できるかと思います。
このように、「IBookDao
」というインターフェースに「BookSqlMapDao
」という実装クラスがDIできることが「DAO Framework」の重要な機能といえるでしょう。
各レイヤーの役割について
今回のサンプルで紹介してきた「DAO Framework」は多層アプリケーションを前提にしています。設計や実装を行う場合には、以下のレイヤごとの役割を明確に理解しておくことが重要になります。
レイヤ | 役割 | 使用パターン |
プレゼンテーション層 | Form/Webによるデータの表示や入力。フォーマットチェック。 | なし |
サービス層 | ビジネスロジックの実装。トランザクションの管理。 | Singlton/ファサード |
パーシステンス層 | データベース(リソース)アクセス。CRUDの実装。 | DAO/DI/ファサード |
これらのレイヤの役割を理解した上で、「DAO Framework」をうまく利用すると、拡張や保守に強い多層アプリケーションを構築することができるでしょう。
iBATIS.NETのデバッグについて
もしiBATIS.NETが正しく動作しない場合は、XMLの設定に誤りがある可能性が高いため、実行時のエラーメッセージを確認してください。log4netのログ出力メソッドの2つ目の引数に、キャッチした例外を設定することで詳細のログが見れます。
原因判明が困難な場合はiBATIS.NETのソースコードをダウンロードし、参照設定をDLLではなくダウンロードしたプロジェクトに変更します。そしてメニューの[デバッグ]-[例外]のダイアログにて[例外がスローされた時]の設定を[デバッガで中断]に変更します。この設定をしておけば、エラー発生ポイントで実行が停止するので原因を追求しやすいでしょう。
まとめ
iBATISの特長をまとめると以下のようになります。
- 「DAO Framework」の
DaoManager
によりサービス層のプログラムがシンプルになる。また、パーシステンス層とサービス層の間はインターフェースベースでの接続をしているので、パーシステンス層の変更や修正による影響を受けにくい。 - 「SQL Maps」により、パーシステンス層の開発者はマッピングファイルにてSQLを集中管理することができる。また、サービス層・プレゼンテーション層の開発者はSQLを意識せずにオブジェクトによるアプリケーション構築に専念できる。
- SQLはマッピングファイルに定義されているため、軽微なSQLの修正であれば、アプリケーションの修正が不要である。
- 活発なオープンソースプロジェクトであるApacheのプロダクトであり、Javaにも存在するため、Java業務への応用が図れる可能性がある。
iBATISには、以上のような長所がありますので、興味を持たれた場合には、マニュアル、チュートリアル、NPetStore、ソースコード付属のテストコードなどをご覧になってみてはいかがでしょうか。
参考資料
- Apache iBATIS(英語)
- 上級DAOプログラミング(developerWorks)
- Inversion of Control コンテナと Dependency Injection パターン(Martin Fowler)
- ドメインロジックとSQL(Martin Fowler)
- J2EEパターン~明暗を分ける設計の戦略(ピアソン・エデュケーション)
- .NETのアプリケーション アーキテクチャ(日経BPソフトプレス)