読者です 読者をやめる 読者になる 読者になる

give IT a try

プログラミング、リモートワーク、田舎暮らし、音楽、etc.

O/Rマッピングツールに対する誤解をときたい -実装編 Part8-

Part7を読む

NHibernateに関する補足説明

できればサンプルプログラムを1から10まで説明していきたいのですが、そうするとボリュームが凄いことになってしまうので、注目すべき部分のみをピックアップしてみたいと思います。

Lazy Loading機能

Lazy LoadingについてはPart6Part7で少し言及しました。
SQLではテーブルをJOINして複数のテーブルにまたがるデータを一度に取得するのが普通ですが、NHibernateでは必要になったタイミングで必要なデータを取得するというのがデフォルトの挙動です。


これを聞くと「なんて非効率な!」と思われる人が確実に出てきますが、大量のデータを一度に処理したり、Criticalなパフォーマンス要件が存在したりするのでなければ、それほど問題にはならないはずです。
Lazy Loading機能を活用すれば、明示的に取得するのは親(ルート)となるオブジェクトだけで良くなり、子や孫のオブジェクトは芋づる式にNHibernateが自動取得してくれるので、コードをシンプルに保てます。


f:id:JunichiIto:20101223052329p:image:w600


画面遷移図の一番右下にある注文情報ページを見てください。
この画面は前の画面から注文IDを受け取って注文情報の詳細を表示します。


f:id:JunichiIto:20101223052810p:image:w600


クラス図はこのようになっています。
注文クラスが親と考えると、注文明細と書籍がそれぞれ子と孫、という関係になっていますね。
このページを表示するためには親、子、孫の全オブジェクトが必要になります。


先ほどもお話しした通り、子や孫はNHibernateが自動的に取得してくれるので、必要になってくるのは親を取得するコードだけです。
注文Facadeの実装は以下のようになります。

// 指定された注文IDの注文情報を返す
public BookOrder FindBookOrderByID(int bookOrderID)
{
    ISession session = NHibernateHelper.GetCurrentSession();
    return session.Get<BookOrder>(bookOrderID);
}


画面側のコードを載せると長くのでここには載せませんが、Lazy Loadingのイメージにはこんな感じです。

// 注文を取得する
BookOrderFacade bookOrderFacade = new BookOrderFacade();
BookOrder bookOrder = bookOrderFacade.FindBookOrderByID(123);

// 注文明細を取得する (NHibernateが自動取得)
IList<BookOrderItem> bookOrderItems = bookOrder.Items;
foreach (BookOrderItem bookOrderItem in bookOrderItems)
{
    // 書籍を取得する (NHibernateが自動取得)
    Book book = bookOrderItem.Book;
}


SQLを確認すると、確かにSQLが別々に発行されています。

-- 注文の取得
SELECT bookorder0_.book_order_id as book1_0_0_, bookorder0_.customer_name as customer2_0_0_, bookorder0_.order_date as order3_0_0_ FROM book_order bookorder0_ WHERE bookorder0_.book_order_id=@p0;@p0 = 449

-- 注文明細の取得
SELECT items0_.book_order_id as book4_1_, items0_.book_order_item_id as book1_1_, items0_.item_order as item5_1_, items0_.book_order_item_id as book1_2_0_, items0_.shipping_date as shipping2_2_0_, items0_.book_id as book3_2_0_ FROM book_order_item items0_ WHERE items0_.book_order_id=@p0;@p0 = 449

-- 書籍の取得
SELECT book0_.book_id as book1_1_0_, book0_.book_name as book3_1_0_, book0_.stock_count as stock4_1_0_, book0_.book_type as book2_1_0_ FROM book book0_ WHERE book0_.book_id=@p0;@p0 = 1238
SELECT book0_.book_id as book1_1_0_, book0_.book_name as book3_1_0_, book0_.stock_count as stock4_1_0_, book0_.book_type as book2_1_0_ FROM book book0_ WHERE book0_.book_id=@p0;@p0 = 1242


コード量が少なくなればコードの可読性が高まり、保守性も上がります。
またコードが少ないほど不具合の発生率も低くなります。
それだけではなく、開発者は「親、子、孫、どこまでデータを事前に取得しておけばいいのか?どうやって取得すればいいのか?」と悩む必要がなくなります。


SQL原理主義的な観点からすれば「こんなSQLは使い物にならん!」という風に見えるかもしれませんが、決してデメリットばかりではないということを理解してもらえると嬉しいです。


ちなみにSessionがクローズされてしまうとLazyLoadingが機能しなくなるので注意が必要です。
Part6Part7に書いていたのはこのことだったわけです。

ポリモーフィックなクエリ

Part6ではHQLに関して以下のような説明をしました。

ちなみに和書だけを抽出したいなら「from JapaneseBook」となります。
同じく洋書を抽出する場合は「from ForeignBook」です。

書籍から和書、洋書へと進むと親クラスからサブクラスへ進むことになり、対象となるデータが限定されますが、逆に書籍から親クラスへ進んだ場合はどうなるでしょうか?
つまりこうです。


from System.Object


一見何のことだか分からないと思いますが、これは「NHibernateの管理下にあるすべてのオブジェクト」を意味することになります。
よって書籍も注文も注文明細も含めて全部です。
通常、こんなクエリがあっても役には立ちませんが、実はユニットテストコードを書く上では便利だったりします。

[SetUp]
public void SetUp()
{
    // DB上の全テーブル、全データを削除する
    NHibernateHelper.ExecuteTransaction(delegate(ISession session)
    {
        string hql = "from System.Object";
        session.Delete(hql);
    });

    // テストデータを作成する
    this.CreateTestData();
}


こんな感じでテストデータ作成前の全件削除に使えたりします。
もちろん、親子関係を考慮しながら削除してくれるので、削除中に外部キー制約違反が発生したりすることもありません。
こういった芸当はNHibernateならでは、という感じです。

selectを指定するHQL

HQLではselectが不要、というのはPart6で説明した通りです。

よって「select *」みたいな記述は原則不要となります(必要に応じてselectを書くこともできますがここでは割愛します)。

「selectを書くことができる」とも書きましたが、では一体どういうときに使うのでしょうか?
サンプルプログラムでも使っている場所があるので、ひとつ例を紹介してみたいと思います。


f:id:JunichiIto:20101223052329p:image:w600


注文履歴一覧ページには注文者を選択するドロップダウンリストがあります。
このリストを作成するためには注文情報から重複のない注文者のリストを抽出する必要があります。
注文Facade内のメソッド実装は以下のようになります。

// 注文者の一覧を返す
public IList<string> FindCustomorNames()
 {
    string hql = @"select   distinct 
                            bookOrder.CustomerName 
                   from     BookOrder as bookOrder 
                   order by bookOrder.CustomerName";
    ISession session = NHibernateHelper.GetCurrentSession();
    IQuery query = session.CreateQuery(hql);
    return query.List<string>();
}


このように書くと重複のない注文者のリストを抽出することができます。
しかしselectで必要な項目だけを取得すると、クラスのインスタンスとしてデータを取得することはできません。
あまり頻繁にselectを使うようであればクラス設計を見直す必要があるかもしれません。

パフォーマンス問題について

O/Rマッピングツールにはメリットもデメリットもあります。
その中でも実行パフォーマンスについてはよく批判される部分だと思います。
しかし、まず理解してもらいたいのはO/Rマッピングツールにも様々なメリットがあるということです。
ただし、「SQLを書かなくても良い」ことが一番のメリットと勘違いしてはいけないことは、何度も何度も繰り返し述べてきている通りです。


さてパフォーマンス問題ですが、ツールの開発者もそれをそのまま放置しておくほど愚かではありません。
世間でよく遭遇する問題については色々と対処してくれています。
NHibernateに関していえば、以下のページで具体的なパフォーマンスの向上戦略を説明してくれています。


http://nhforge.org/doc/nh/en/index.html#performance


パフォーマンスの問題に限らず、実際の開発業務でNHibernateを使い始めると「こういうときはどう実装したらいいの?」とか「どうしてこんな挙動をするのか理解できない」といった問題によく遭遇すると思います。
NHibernateで本格的な開発をする場合は以下のようなマニュアルや書籍を精読し、すぐに問題を解決できる知識を貯えておく必要があります。


NHibernate - Relational Persistence for Idiomatic .NET


Nhibernate in Action

Nhibernate in Action


NHibernateに関する日本語の情報はまだまだ少ないと思うので、Java版のHibernateに関する情報を調べてみるのもありだと思います。


HIBERNATE イン アクション

HIBERNATE イン アクション

  • 作者: Christain Bauer,Gavin Ki,倉橋央,勝嶌和彦
  • 出版社/メーカー: ソフトバンク クリエイティブ
  • 発売日: 2005/12/28
  • メディア: 大型本
  • 購入: 3人 クリック: 104回
  • この商品を含むブログ (34件) を見る


Documentation - Hibernate - JBoss Community
※日本語版のReferenceも載っています。


またNHibernateHibernateの機能に精通することで「これは便利!」とか「こんな機能があったのか」と開発業務をさらに効率化してくれるテクニックに気づくこともあると思います。
「だからO/Rマッピングツールは使えない」とぼやく前に、まずは徹底的にツールについて研究してみましょう。



さて、NHibernateの話をするだけでかなりの文量になってしまったので、これまでのまとめは次回にしたいと思います。