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

give IT a try

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

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

NHibernate OOP

Part5を読む

NHibernateの利用を簡略化するヘルパークラス(NHibernateHelper.cs)

書籍Facadeの説明をする前にヘルパークラスの説明をしたいと思います。
実装コードの全体を載せる前にまずはクラスのAPIから紹介します。

namespace Junichi.Ito.NHibernateBookStore
{
    public static class NHibernateHelper
    {
        // トランザクション実行時に個別の処理を実装するデリゲート
        public delegate void Action(ISession session);

        // 汎用的なトランザクション処理を実行する(更新処理で使用)
        public static void ExecuteTransaction(Action action)

        // 現在のSessionを取得する
        public static ISession GetCurrentSession()

        // 現在のSessionをクローズする
        public static void CloseSession()

        // Consoleの標準出力をVisual StudioのDebugウインドウに切り替える
        public static void SetConsoleOutToDebugWindow()
    }
}


前回紹介した注文Facadeクラスで使用したのはExecuteTransactionメソッドとGetCurrentSessionメソッドです。


CloseSessionメソッドはその名の通り、NHibernateのSessionをクローズします。
ただし、SessionをクローズするとNHibernateのLazy Loading機能が働かずにエラーが出たりするので、ASP.NET側のglobal.asaxでリクエスト処理終了時にクローズしています。


SetConsoleOutToDebugWindowメソッドは開発者向けのヘルパーメソッドです。
プログラムを実行する上では必要ありません。


それでは実装コードの全体です。

using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using NHibernate;
using NHibernate.Cfg;

namespace Junichi.Ito.NHibernateBookStore
{
    // NHibernate用のヘルパークラス
    // 注) このクラスは下記ページを参考にした
    //     http://nhforge.org/doc/nh/en/index.html#quickstart-playingwithcats
    public class NHibernateHelper
    {
        // NHibernateのSessionFactory
        private static readonly ISessionFactory sessionFactory;

        // NHibernateのSession
        // 注) ただのstatic変数は複数のスレッドからアクセスされた際に問題が発生するので
        //     ThreadLocalなstatic変数としておく
        [ThreadStatic]
        private static ISession currentSession = null;

        // staticコンストラクタ
        static NHibernateHelper()
        {
            // 生成コストが高価なのでシングルトンとする
            sessionFactory = new Configuration().Configure().BuildSessionFactory();
        }

        // トランザクション実行時に個別の処理を実装するデリゲート
        public delegate void Action(ISession session);

        // 汎用的なトランザクション処理を実行する(更新処理で使用)
        public static void ExecuteTransaction(Action action)
        {
            // トランザクションを開始する
            ISession session = GetCurrentSession();
            ITransaction transaction = session.BeginTransaction();

            // 個別の処理を実行する
            action(session);

            // トランザクションをコミットする
            transaction.Commit();
        }

        // 現在のSessionを取得する
        public static ISession GetCurrentSession()
        {
            if (currentSession == null)
            {
                currentSession = sessionFactory.OpenSession();
            }

            return currentSession;
        }

        // 現在のSessionをクローズする
        public static void CloseSession()
        {
            if (currentSession == null)
            {
                return;
            }

            currentSession.Close();
            currentSession = null;
        }

        // Consoleの標準出力をVisual StudioのDebugウインドウに切り替える
        public static void SetConsoleOutToDebugWindow()
        {
            Console.SetOut(new DebugWriter());
        }

        // Debugウインドウに文字列を書き込むためのクラス
        private class DebugWriter : TextWriter
        {
            // 抽象メソッドの実装
            public override Encoding Encoding
            {
                get { return null; }
            }

            // WriteLineメソッドをオーバーライドする
            public override void WriteLine(string value)
            {
                Debug.WriteLine(value);
            }
        }
    }
}


ExecuteTransactionメソッドは一種のTemplate Methodパターンです。
メソッドでは共通となるロジックを実装し、個別に異なる処理は引数のデリゲート(Action action)で実装します。


あとはcurrentSessionがスレッド別に管理されるstatic変数になっているところも人によっては珍しく見えるかもしれません。
共有変数も、pubulic static宣言していまう」とか危険すぎますからね(苦笑)。
でも「sessionFactory」はスレッド別に管理しなくてもいいみたいです。


ヘルパークラスですし、NHibernateのサイトからコピーしたコードも多いのでこのクラスに関する説明は以上で終わりたいと思います。

書籍Facadeクラス(BookFacade.cs)

さて、次は書籍Facadeクラスです。
このクラスはPart3のaspxファイル内でObjectDataSourceによって呼び出されていたクラスです。

using System;
using System.Collections.Generic;

using NHibernate;

namespace Junichi.Ito.NHibernateBookStore.Facade
{
    // 書籍用Facadeクラス
    public class BookFacade
    {
        // すべての書籍を返す
        public IList<Book> FindAllBooks()
        {
            string hql = @"from     Book as book 
                           order by book.BookName";
            ISession session = NHibernateHelper.GetCurrentSession();
            IQuery query = session.CreateQuery(hql);
            return query.List<Book>();
        }

        // 他のコードは省略
    }
}


前回紹介した注文Facadeでは書籍を1件だけ取得していましたが、こちらはドロップダウンリスト用にすべての書籍をリストとして取得しています。


おそらく皆さんが目に付くのは以下の部分だと思います。

string hql = @"from     Book as book 
               order by book.BookName";


これはHibernateNHibernateで使用されるHQLというクエリ言語です。


「奇妙なSQLだな〜」と思ったそこのあなた!!


・・・最初は仕方がないかもしれません(苦笑)。
ただし、これは「あくまで構文をSQLに似せただけのオブジェクト操作言語」です。


SQLなら「select カラム名」となりますが、このHQLでは「select」が省略されています。
オブジェクトを完全に復元するしようとすると結局すべてのプロパティが必要になるので、必要なプロパティだけを指定する意味はありません。
よって「select *」みたいな記述は原則不要となります(必要に応じてselectを書くこともできますがここでは割愛します)。


selectが不要な理由は今説明したので、HQLの本質の部分を説明していくことにしましょう。
HQLは確かにSQLっぽいです。
SQLっぽいので、つい「HQL=SQL」と考えてしまい、不必要にHQLをSQLに近づけてしまう開発者も多いと思います。
しかし、HQLはあくまでクラスやオブジェクトを操作するためのミニ言語になっています。
HibernateNHibernateを使うならHQLとSQLは似て非なるもの、という考えをもった上でHQLの扱い方を勉強した方が良いと思います。


で、ここで重要なのはやはり「主役はオブジェクト」ということです。
「from Book as book」に出てくる「Book」はクラス名です。
テーブル名ではありません。
繰り返しになりますが、ここまでの説明でデータベースのテーブル設計に言及した箇所は全くありません。
すべてクラスの話ばかりをしてきたはずです。
同じく「order by book.BookName」の「BookName」も書籍クラスのプロパティ名です。


つまり「from Book as book order by book.BookName」というHQLは「保存されている書籍オブジェクトを書籍名順に並べて返せ」とNHibernateに命令していることになります。


HQLはテーブルを操作するSQLではなく、オブジェクトを操作する言語であるというのは「from Book as book order by book.BookName」を以下のように読み替えるといいかもしれません。

// from Book as book
Book book = new Book();

// order by book.BookName
string orderBy = book.BookName;


まあ実際は書籍クラスは抽象クラスなので実際には「new Book()」とは書けませんが。


また前回の話の繰り返しになりますが、「return query.List();」で返却される書籍クラスのインスタンスは実際には和書か洋書のいずれかになります。
オブジェクトを復元する過程でNHibernateが和書か洋書かを自動判別するので、開発者は和書と洋書の違いを意識する必要がありません。


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


HQLがオブジェクトを操作するための言語であることが理解できている人はSQLのようにwhere句か何かでデータをフィルタリングする必要がない理由がわかると思います。


書籍Facadeクラスの説明は以上です。
今回はHQLの話を中心に説明しました。


さて、次回はよぉ〜〜〜〜〜〜やくデータベース関連の説明です。


Part7を読む