2009年9月2日水曜日

Monoで他のDBも使ってみたよ!+(db4o、NHibernate編)4

このエントリーをはてなブックマークに追加

8.NHibernate編

これまで、主にRDBMSを対象にしたDBアクセスについて解説を行いましたが、NHibernateはRDBMSではありません。

 NHibernateはオブジェクト関係(O/R)マッピングフレームワークと言われるもので、言い換えれば、NHibernateを経由したDBアクセスについて解説を行う、ということになります。

 Wikipediaでは、オブジェクト関係マッピングについて
データベースとオブジェクト指向プログラミング言語の間の非互換なデータを変換するプログラミング技法である。
と解説しています。もう少し簡単に言うとしたら、RDBへのアクセスをあたかもdb4oのようなODBへのアクセスのように行う方法、でしょうか。

 NHibernateではマッピング先のRDB上のテーブルへのデータ登録を次の様なコードで実現しています。
Configuration cfg       = new Configuration();
cfg.Configure();
ISessionFactory factory = cfg.BuildSessionFactory();
ISession session        = factory.OpenSession();
Product product         = new Product{ Id = 1, Name = "Clamp", Price = 12.48M, Description = "Workbench clamp" };
session.Save(product);
また、登録したデータの検索は
var result = session.Get<Product>(1);
で可能です。

 NHibernateは複数のRDBMSをサポートし、これまでの記事で使用したすべてのRDBMSで使用可能です。本稿では、NHibernateを使用した、RDBMSへのDBアクセスについて解説を行います。NHibernateについてはWikipediaなどを参照して下さい。

セットアップ

NHibernateの動作に必要なアセンブリの導入を行います。各RDBMSのセットアップについては、これまでの記事を参照して下さい。ただ、本稿での動作環境は、以前と異なる部分があるので、セットアップ上の変更点については、「おまけ」の節で解説します。

アセンブリの導入

NHibernateはSourceForgeのNHibernateプロジェクトページからダウンロード可能です。現時点(2009.09.02)の最新版は2.1.0.GAです(NHibernate 2.1.0はHibernate 3.2.6に相当)。

 「NHibernate-2.1.0.GA-bin.zip」を任意のディレクトリで展開し、次のアセンブリをGACへインストールまたは環境変数MONO_PATHに設定されているディレクトリへコピーします。

[必要なアセンブリ]
アセンブリ概要
NHibernate.dllNHibernate Service Provider
Iesi.Collections.dllrequired from NHibernate
log4net.dllrequired from NHibernate
NHibernate.ByteCode.LinFu.dlneed for proxyfactory.factory_class
LinFu.DynamicProxy.dllneed for proxyfactory.factory_class

[導入例(MONO_PATH)]
$ echo $MONO_PATH
/home/sta/lib
$ unzip NHibernate-2.1.0.GA-bin.zip -d NHibernate-2.1.0.GA-bin/
$ cd NHibernate-2.1.0.GA-bin/Required_Bins
$ cp *.dll ~/lib
$ cd ../Required_For_LazyLoading/LinFu
$ cp *.dll ~/lib

コンフィグファイル

RDBMSへの接続情報等を設定します。「NHibernate-2.1.0.GA-bin.zip」には、各RDBMS用NHibernateコンフィグファイルのテンプレートが含まれています(「展開先/Configuration_Templates」配下)。それらを参考にして、各RDBMS用のコンフィグファイルを用意します(以下は、SQLiteを対象にした設定例になります)。

 作業する上でのディレクトリ構造は、下表の通りとします。

ディレクトリファイル
~/src/cs/NHibernateExample/DriverMonoDataSqliteDriver.cs
~/src/cs/NHibernateExample/TestSQLite.cfg.xml

[NHibernateコンフィグファイル例(SQLite.cfg.xml:SQLite用)]
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory name="NHibernateExample">
    <property name="connection.driver_class">NHibernateExample.Driver.MonoDataSqliteDriver, NHibernateExample</property>
    <property name="connection.connection_string">Data Source=/home/sta/data/sqlite/TestData.db</property>
    <property name="show_sql">false</property>
    <property name="dialect">NHibernate.Dialect.SQLiteDialect</property>
    <property name="query.substitutions">true=1;false=0</property>
    <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
    <mapping file="Product.hbm.xml" />
  </session-factory>
</hibernate-configuration>
コンフィグファイルの読み込みは、
Configuration cfg = new Configuration();
cfg.Configure();
とした場合、実行プログラムと同じディレクトリにある「hibernate.cfg.xml」が読み込まれ、
cfg.Configure("SQLite.cfg.xml");
とした場合、同じディレクトリにある任意のファイル「SQLite.cfg.xml」が読み込まれます。

 SQLite用コンフィグファイルのテンプレートでは、SQLite用データプロバイダを使用するためのドライバとして、「NHibernate.Driver.SQLiteDriver」が設定されていますが、同ドライバで呼び出しているのは「SQLite.NET」であり、Monoで標準提供されている「Mono.Data.Sqlite」ではありません。ということで、次のような「Mono.Data.Sqlite」用のドライバを用意する必要があります。

[Mono.Data.Sqlite用ドライバ(MonoDataSqliteDriver.cs)]
using System;
using NHibernate.Driver;

namespace NHibernateExample.Driver
{
  public class MonoDataSqliteDriver : ReflectionBasedDriver
  {
    public MonoDataSqliteDriver() : base(
      "Mono.Data.Sqlite",
      "Mono.Data.Sqlite.SqliteConnection",
      "Mono.Data.Sqlite.SqliteCommand")
    {
    }

    public override bool UseNamedPrefixInSql
    {
      get { return true; }
    }

    public override bool UseNamedPrefixInParameter
    {
      get { return true; }
    }

    public override string NamedPrefix
    {
      get { return "@"; }
    }

    public override bool SupportsMultipleOpenReaders
    {
      get { return false; }
    }
  }
}
また、NHibernateではデータプロバイダの呼び出しにReflection(System.Reflection.Assembly.Load)を使用しており、GACにインストールされているアセンブリを呼び出すために、App.config(foo.exe.config)に次の設定が必要になりますが、
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <qualifyAssembly partialName="Mono.Data.Sqlite"
        fullName="Mono.Data.Sqlite, version=2.0.0.0, publicKeyToken=0738eb9f132ed756, culture=neutral" />
    </assemblyBinding>
  </runtime>
</configuration>
上記設定はMono 2.4上では有効になりませんでした(未対応のようです)。そこで、必要なアセンブリのリンクをMONO_PATHで設定されているディレクトリに作成して、動作確認を行いました。

$ cd ~/lib
$ ln -s /opt/mono/2.4/lib/mono/2.0/Mono.Data.Sqlite.dll .

マッピング

次のようなProductsテーブルに関連付けられる、Productクラス、NHibernateマッピングファイルを用意します。

ディレクトリファイル
~/src/cs/NHibernateExampleProduct.cs
~/src/cs/NHibernateExample/TestProduct.hbm.xml

[Productsテーブル]
create table Products(
  ProductID int not null primary key,
  ProductName text not null,
  Price numeric null,
  ProductDescription text null);

[Productクラス(Product.cs)]
namespace NHibernateExample
{
  public class Product
  {
    int      _id;
    string   _name;
    decimal? _price;
    string   _description;

    public virtual int Id
    {
      get { return _id; }
      set { _id = value; }
    }

    public virtual string Name
    {
      get { return _name; }
      set { _name = value; }
    }

    public virtual decimal? Price
    {
      get { return _price; }
      set { _price = value; }
    }

    public virtual string Description
    {
      get { return _description; }
      set { _description = value; }
    }

    public override string ToString()
    {
      return "ID:" + _id + " NAME:" + _name + " PRICE:" + _price + " DESCRIPTION:" + _description;
    }
  }
}

[NHibernateマッピングファイル(Product.hbm.xml)]
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" 
  assembly="NHibernateExample" 
  namespace="NHibernateExample">

  <class name="Product" table="Products">
    <id name="Id" column="ProductID">
      <generator class="assigned" />
    </id>
    <property name="Name" column="ProductName" not-null="true" />
    <property name="Price" column="Price" />
    <property name="Description" column="ProductDescription" />
  </class>

</hibernate-mapping>
マッピングファイルはNHibernateコンフィグファイル内で指定し、
<mapping file="Product.hbm.xml" />
Productクラスはライブラリとしてビルドします。

$ gmcs -t:library -r:NHibernate.dll -out:./Test/NHibernateExample.dll Product.cs -recurse:./Driver/*.cs

 Monoで他のDBも使ってみたよ!+(db4o、NHibernate編)5 へ続く。

0 件のコメント:

コメントを投稿