13 July 2009
ページ ツール |
ColdFusionとデータベースの概念に関する知識があれば役立ちます。
中級
Transferフレームワークの作者である私は、ColdFusionオブジェクトリレーショナルマッピング(ORM)と、パーシステンスフレームワーク一般について、かなり前から興味を抱いてきました。ColdFusion 9ベータ版に、Javaの主要なORMフレームワークの1つであるHibernateとの統合が組み込まれると聞いて、私は、これにより様々な分野のColdFusion開発者に提供されることになる新機能をぜひ試してみたいと思いました。
私がすぐに感じたのは、HibernateとColdFusionの統合が非常にうまく実現されているということです。Hibernate(およびORM一般)の実装の複雑さの多くを隠蔽しながら、開発戦略に関する高い柔軟性を開発者に残しており、さらにHibernateの複雑で設定可能な機能を直接利用したい開発者のために、Hibernateのレイヤの多くを公開しています。
この記事では、ORM一般と、ColdFusionとHibernateの統合について紹介してから、ORMによるデータベース操作の実行と関係の管理の方法を説明したいと思います。
オブジェクトリレーショナルマッパーという言葉からは、何か複雑怪奇なものを想像するかもしれませんが、実際には核となる概念はきわめて単純でわかりやすいものです。問題の本質は、アプリケーションの場合、オブジェクト同士の関係はメモリ上の参照という形を取るのに対して、リレーショナルデータベースの場合、データのテーブルは主キーと外部キーの関係を通じて互いを参照するということです。例えば、ミュージシャンオブジェクトは、自分が演奏できる楽器の配列を保持しています。これに対して、データベースの場合、ミュージシャンテーブルには主キーがあり、これが外部キーから参照されることによって、ミュージシャンと楽器との間の関係が構築されます。
ORMの役割は、オブジェクトとリレーショナルデータとの間の相互の変換を行うことで、開発者が面倒な作業を自分で行わなくて済むようにすることです。多くのORMはこの核心的機能以外にも様々な機能を備えていますが、本質的には上に述べたことがすべてであると言えます。
ORMの説明は理論的には納得できるかもしれませんが、開発者にとっては実際にどんな利益があるのでしょうか。基本的に、ORMを使用する最大の理由は、アプリケーション開発の時間を節約できることです。もちろん、新しいツールの使い方を覚えるには必ず時間がかかるのですが、この点は非常に重要なのでもう一度強調しておきます。ORMを使えば、アプリケーション開発の時間を大幅に節約できます。
その要因として、まずアプリケーションを最初に開発する際の実装時間自体の短縮があります。ORMを使えば、作成、読み取り、更新、削除のためのSQL文や、それを使用するためのColdFusionコードを作成する膨大な手間を省けるからです。それだけでなく、ORMはアプリケーションでエラーが発生するおそれがある場所を大幅に減らす効果があります。すべてのパーシステンスニーズに対して設定ポイントが1つしか存在しないからです。ORMを使えば、INSERT文は正しいのに、UPDATE文にスペルの誤りがあるといった問題は過去のものになります。
保守の時間も短縮されます。例えば、オブジェクトにプロパティを1つ追加しようとすると、UPDATE文、SELECT文、INSERT文、さらにはColdFusionコードまで、無数の箇所の変更が必要になるのが普通です。このような変更はすべて、プログラミングエラーを引き起こすおそれがあります。ORMを使えば、1か所で1つの変更を行うだけで済むのが普通です。
Hibernate Webサイトには、「Hibernateとは、強力でハイパフォーマンスのオブジェクト/リレーショナルパーシステンスおよびクエリーサービスです」と説明されています。これはまた、現在最も広く使用されているJavaパーシステンスソリューションの1つでもあります。
Hibernateは、リレーショナルデータベースにオブジェクトを出し入れするために必要な作成、読み取り、更新、削除(CRUD)のすべてのツールを、洗練されたマッピング構成によって提供します。また、拡張クエリー、データベーススキーマの生成、キャッシング、イベント捕捉といったツールも装備しています。
長期的には、Hibernateの動作の仕組みについて詳しく学ぶことには、大いに意味があります。ColdFusionが内部的に何をしているかを理解しておけば、何かやっかいな問題が起きても解決できる可能性があるからです。ただし今のところは、細かいことは何も知らなくてもORM統合を使用することができます。複雑な作業のほとんどは、ColdFusion開発チームがユーザーに代わって既に済ませているからです。
ColdFusion 9ベータ版が登場する前は、ColdFusionアプリケーションでHibernateを使用しようとすると、JavaまたはGroovyのどちらかを使いこなせる必要がありました。また、Javaのクラスパス、Hibernate設定、Hibernateセッション管理、トランザクション管理、およびHibernateの強力さと同時に管理の複雑さの源泉でもある様々な詳細項目を、自分で設定して面倒を見る必要がありました。
しかし、ColdFusion 9ベータ版の登場によって、JavaやGroovyの知識は不要となりました。HibernateとColdFusion 9ベータ版の統合のおかげで、上記の要素の多くは自動的に管理されるか、既になじみのある方法で開発者に提供されるようになりました。ColdFusionコンポーネントとApplication.cfcを通じてORMを使用し、設定することで、一般的なColdFusion開発者がHibernateの威力と柔軟性を利用できるようになったのです。
ORM統合の設定の最初の段階は、アプリケーションのApplication.cfcを通じて実行します。
ORMを有効にするには、次の行を挿入します。
this.ormenabled = true;
これでHibernateが使用可能になります。
ColdFusion 9ベータ版の新しい拡張を利用するには、Application.cfc内のすべての<cfquery>呼び出しのデフォルトのデータソースを、次の行によって設定します。
this.datasource = "orm";
これは、ormデータソースを使用するようにアプリケーションに指示すると共に、どのデータソースに接続するかをHibernateに指示します。
注意:Hibernate統合は、1つのColdFusionアプリケーション内で1つのデータソースだけを処理できます。Hibernateは一度に1つのデータベースしか管理できないので、この制約はそれほど驚くべきものではありません。<cfquery>を使えば他のデータソースにアクセスすることは可能ですが、この制約については念頭に置いておく必要があります。
設定に関しては、他にすべきことは何もありません。ただし、Hibernateの動作を制御するために使用できるいくつかのオプション属性が他にあります。これらの属性は、this.ormsettingsによって提供されます。これはApplication.cfcで設定されるオプション値のstructです。このサンプルでは、まず使用するCFCが/comフォルダーに存在することをORMに通知します。これにより、ORMがWebルート全体を検索しなくて済むので、起動が高速になります。
this.ormsettings.cfclocation = "com";
また、Hibernateが使用するdialectを設定します。これは、管理するデータベースの種類をORMに通知するものです。これは通常は自動的に設定されるので気にする必要はありませんが、このサンプルでは、mySQLを使用して、Hibernateにデータベースを作成させます。トランザクションおよび外部キーのサポートのために、テーブルはデフォルトのMyIsamでなくInnoDBとして作成させるので、この値を設定する必要があるのです。
this.ormsettings.dialect = "MySQLwithInnoDB";
また、dbcreateおよびlogSQL設定も使用します。これらは開発サーバー上でのみ実行します。
データベーステーブルをHibernateに作成させることにしたので(アプリケーションのオブジェクトの作成に集中するため)、どのように作成するかを指示する必要があります。dbcreateオプションをupdateに設定した場合、必要なテーブルが存在しない場合は作成され、既に存在する場合は更新されます。しかしここでは、実行の際に使用するデータのセットが正確にわかるように、アプリケーションを開始するたびに初期状態のデータベースから開始するようにします。このために、この属性をdropcreateに設定することにより、テーブルが存在する場合は削除して作成し直すようにHibernateに指示します。
this.ormsettings.dbcreate = "dropcreate";
最後に、生成するすべてのSQLをデバッグ用にログに記録するようにHibernateに指示します。
this.ormSettings.logSQL = true;
ただしこの場合、<cfquery>とは違って、SQLのログはデバッグビューでなくコンソールに出力されます。これは、ColdFusionを、Windowsサービスなどではなく、コマンドラインから起動している場合には、大きな問題ではありませんが、コマンドラインから起動していない場合は、ColdFusion Builderベータ版を通じてColdFusionを起動することで、同じ情報を利用できます。
Hibernateを再ロードしたり、テーブルを作成し直したり、マッピング設定を再ロードしたり(これについてはこの後で説明します)したい場合は、ORMReload()メソッドを使用します。これは、アプリケーションを再ロードせずに、ORMだけを再ロードします。別の方法として、ApplicationStop()を使ってアプリケーションを停止することもできます。この場合、次の要求のときにアプリケーションが再ロードされ、結果としてORMも再ロードされます。アプリケーションを再起動するためのユーティリティが、Application.cfcに用意されています。
Application.cfc
<cfcomponent hint="I am the Application CFC" output="false">
<cfscript>
this.name = "ORM Demo";
//turn on orm
this.ormenabled = true;
this.datasource = "orm";
//add some additional settings
this.ormsettings = { cfclocation = "com", dialect="MySQLwithInnoDB"
};
//if this is a development server...
this.developmentServer = true;
if(this.developmentServer)
{
this.ormsettings.dbcreate = "dropcreate"; this.ormsettings.logSQL = true;
}
</cfscript>
<cffunction name="onRequestStart" access="public" hint="Request start processing" returnType="boolean" output="false">
<cfargument name="targetPage" type="string" hint="The page requested" required="true"/>
<cfif StructKeyExists(URL, "reload")>
<cfset ApplicationStop() />
<cflocation url="#arguments.targetPage#">
<cfreturn false />
</cfif>
<cfreturn true>
</cffunction>
</cfcomponent>
Application.cfcでは他にも様々なORM設定オプションが使用できますが、今のところはここで説明したものだけで十分です。詳細を知りたい場合は、ColdFusion 9 Developers Guide—ORM settingsを参照してください。
ColdFusion 9ベータ版のORM統合で基本的な作成、読み取り、更新、削除(CRUD)機能を実行するのは、きわめて簡単です。データベースに保存して使用したいプロパティを持つCFCを定義するだけで、後の作業はHibernateが実行してくれます。例えば、次のCFCはミュージシャンを表します。これに対して名前と年齢を記録します。
<cfcomponent hint="I am a musician" output="false" persistent="true">
<cfproperty name="musicianID" hint="The id for the musician" type="numeric" fieldtype="id"
datatype="integer" generator="identity">
<cfproperty name="name" hint="name of the musician" type="string" length="255">
<cfproperty name="age" hint="Age of the Musician" type="numeric" datatype="integer">
<cffunction name="init" hint="Constructor" access="public" returntype="Musician" output="false">
<cfargument name="name" hint="the name of the musician" type="string" required="no" default="">
<cfscript>
setName(arguments.name);
return this;
</cfscript>
</cffunction>
</cfcomponent>
このサンプルで最初に注目してほしいのは、<cfcomponent>のpersistent属性です。persistentをtrueに設定すると、このCFCがデータベース中のデータを表し、データがORM統合を通じて永続化されることが、ColdFusionに通知されます。次に、新しい暗黙のgetterおよびsetter機能を利用するために、<cfproperty>を使用して、ミュージシャンが持つべきプロパティを指定します。この例では、ミュージシャンの名前であるmusicianIDと、ミュージシャンの年齢です。これにより、ORMは起動時に「Musician」という名前のデータベーステーブルを自動的に作成します。fieldtype="id"、datatype="integer"、generator="identity"と指定したので、Hibernateはテーブルの主キーであるmusicianIDに自動的に増加する番号を設定します。<cfproperty>の定義に従って、「name」と「age」の列も作成されます。nameプロパティでは、length属性によって長さが255に設定されています。ageプロパティでは、datatype属性によって列のデータ型がintegerに設定されています(図1を参照)。
データベースはHibernateが作成してくれるので、いくつかの変更する必要がないデフォルト値を利用できます。例えば、テーブル名は指定する必要がありません。テーブルの値はコンポーネント名、つまりこの例では「Musician」に設定されます。このように、この例ではCFCからデータベーステーブルへのマッピングにわずかな属性しか使用していませんが、実際には他にも多くのオプションが使用できます。既存のデータベース構造をマッピングする場合は、デフォルト値に頼らずに、もっと多くの属性を明示的に指定する必要があるでしょう。ここから後の作業は実に簡単です。新しいミュージシャンを作成するには、次のコードを使用します。
<cfscript>
import com.*;
larry = new Musician("Larry Jingle");
larry.setAge(47);
EntitySave(larry);
</cfscript>
このコードは、単に新しいMusicianオブジェクトを作成し、いくつかのプロパティを設定し、EntitySave()を呼び出します。このメソッドを使えば、SQLをまったく使用せずに、渡されたオブジェクトをデータベース内で永続化できます。これはさらに、テーブルの作成も行ってくれます(図1と図2を参照)。
Larryをロードしてその年齢を変更するには、次のコードを使用します(図3を参照)。
<cfscript>
//larry's ID is 1
larry = EntityLoad("Musician", 1, true);
larry.setAge(48);
</cfscript>
EntityLoad()の第1引数は、ロードするCFCのエンティティ名であり、デフォルトはCFCと同じ名前ですが、変更できます。第2引数は、ロードするミュージシャンのIDです。一意の結果を得るには、第3引数にtrueを指定します。そうしないと、EntityLoad()は1つの値でなく配列を返します。
注意:EntityLoad()を使えば、指定した条件に一致するCFCの配列を得ることができますが、これについてはここでは説明しません。
このコードでは、larryを更新するようにHibernateに明示的に指示してはいません。Hibernateは、どのCFCがロードされたかと、ロードされたCFCが初期状態から変更されたかどうかを把握しています。この例では、Larryのageプロパティが変更されていることをHibernateは知っているので、要求の最後にそれに基づいてデータベースを更新します。Larryに対してEntitySave()を呼び出すこともできますが、オブジェクトがデータベース内で永続化されている場合、Hibernateはこの呼び出しを単に無視します。
注意:パフォーマンス上の理由で、Hibernateは通常、HTTP要求の最後にSQLをまとめて実行します。したがって、コードブロックの途中でEntitySave()を使用しても、挿入SQLがデータベースに対して実行されるのは、通常は要求の最後になります。SQLを強制的に実行させる方法もあります。これについては、ColdFusion 9 Developer Guide—Hibernate session managementを参照してください。
最後に、Larryが不要になった場合は、削除することができます。
<cfscript>
//larry's ID is 1
larry = EntityLoad("Musician", 1, true);
EntityDelete(larry);
</cfscript>
Larryのデータベース内での永続化は解除されます(図4を参照)。
これで、SQLを書くこともテーブルを作成することもなく、作成、読み取り、更新、削除の機能を実行できました。これらの面倒な作業はすべて、HibernateとColdFusion 9ベータ版が行ってくれたのです。
一般的に言って、CFCは単独で用いられるよりも、相互に参照しあうグループで使用されることで威力を発揮します。例えば、ミュージシャンには演奏する楽器の配列が必要です。
ORM統合では、様々な種類の関係をマッピングできます。この記事では、楽器と多対多の関係を持つミュージシャンを扱い、ミュージシャンがグループ内で楽器を共有できるようにすることで、複数のミュージシャンが同じ楽器に同時にアクセスできるようにします。これを出発点として、ColdFusion Hibernate統合でサポートされるあらゆる関係の種類を試してみることができます。
最初に、ミュージシャンコンポーネントの場合と同様に、楽器コンポーネントを作成します。
Instrument.cfc
<cfcomponent hint="An instrument" persistent="true" output="false">
<cfproperty name="instrumentID" hint="id for the instrument" type="numeric"
fieldtype="id" datatype="integer" generator="identity">
<cfproperty name="name" hint="The instrument name" type="string" length="255"/>
<cfproperty name="noise" hint="the noise it makes" type="string" length="255"/>
<cffunction name="init" hint="Constructor" access="public" returntype="Instrument" output="false">
<cfargument name="name" hint="the name of the instrument" type="string" required="no" default="">
<cfargument name="noise" hint="the noise it makes" type="string" required="false" default="">
<cfscript>
setName(arguments.name);
setNoise(arguments.noise);
return this;
</cfscript>
</cffunction>
</cfcomponent>
また、アクセス可能な楽器の配列を保持するため、新しい<cfproperty>をミュージシャンに追加する必要があります。Musician.cfcのコードを次のように変更します。
Musician.cfc
<cfcomponent hint="I am a musician" output="false" persistent="true">
<cfproperty name="musicianID" hint="The id for the musician" fieldtype="id" type="numeric"
datatype="integer" generator="identity">
<cfproperty name="name" hint="name of the musician" type="string" length="255">
<cfproperty name="age" hint="Age of the Musician" type="numeric" datatype="integer">
<cfproperty name="instruments" hint="Array of instruments" singularname="instrument"
fieldtype="many-to-many" collectiontype="array" cfc="com.Instrument"
linktable="musician_instrument" FKColumn="musicianID" inversejoincolumn="instrumentID"
orderby="name">
<cffunction name="init" hint="Constructor" access="public" returntype="Musician" output="false">
<cfargument name="name" hint="the name of the musician" type="string" required="no" default="">
<cfscript>
setName(arguments.name);
return this;
</cfscript>
</cffunction>
</cfcomponent>
instrumentsという名前の新しい<cfproperty>を追加して、新しい多対多のコレクションを定義しています。fieldtype属性を"many-to-many"に設定し、cfc属性を"com.Instrument"に設定します。これはコレクション内のアイテムのためのCFCです。orderby属性設定により、楽器の配列内のすべての楽器は名前の順に並べられます。
注意:この記事の作成時点では、linktable、FKColumn、inversejoincolumnの各属性は必須です。これらはリンクテーブルと外部キーをHibernateに通知する役割を果たします。ただし、これらの属性はオプションとなり、適切なデフォルト値を持つようになる可能性があります。
上記のコードは、ORMの起動時に、多対多のテーブルと、楽器の追加と削除のためのミュージシャンのメソッドを作成します。
<cfscript>
import com.*;
john = new Musician("John Tunes");
john.setAge("51");
EntitySave(john);
//we prepared some instruments earlier
//a flute is instrument 1
flute = EntityLoad("Instrument", 1, true);
//give John the flute
john.addInstrument(flute);
//a piano is instrument 2
piano = EntityLoad("Instrument", 2, true);
//give john the piano
john.addInstrument(piano);
</cfscript>
この例で、addInstrument()メソッドの名前は、多対多構成のsingularname属性セットから得られています。Johnのすべての楽器を表示するには、次のコードを使用します。
<cfoutput>
John's Instruments: <br/>
<cfloop array=#john.getInstruments()# index="instrument">
I am a #instrument.getName()#, and I make the noise
#instrument.getNoise()# <br/>
</cfloop>
</cfoutput>
上記のコードは、図5に示す出力を生成します。
図6に示すInstrument、Musician、musician_instrumentの3つのテーブルは、自動的に作成されたものです。
Instrumentテーブル(図7を参照)からピアノとフルートをJohnに追加すると、musician_instrumentテーブルで関係が永続化されます(図8を参照)。
ColdFusion Hibernate統合を使えば、アプリケーションを構築する際に、データの永続化に必要な開発者の手間を大幅に減らすことができます。この記事で紹介した例は、ほんの入り口に過ぎません。この記事では紹介していないHibernateの柔軟性の高い機能の数々が、ColdFusion製品に組み込まれています。例えば、クエリー、キャッシュ、パーシステンスイベント処理といったユーティリティや、その他のアプリケーションデータ管理のための様々なツールがあります。しかしながら、最も基本的な組み込みORM機能を使用するだけでも、ColdFusionアプリケーションの開発の速さと効率を大幅に改善できます。
ColdFusionのORM機能について詳しくは、次のサイトを参照してください。
Tutorials & Samples |
ColdFusion Blogs |
More |
| 02/09/2012 | ColdFusion ORM book now in Kindle and EPUB format |
|---|---|
| 02/08/2012 | Adobe eSeminar on ColdFusion and Monitoring |
| 02/07/2012 | ColdFusion offer from Intergral |
| 02/05/2012 | CF101 Archive: October 2007 Every Beginner must Grow Up |
ColdFusion Cookbooks |
More |
| 07/27/2011 |
Passing a list with |
|---|---|
| 05/27/2011 | AUTOMATED SANITIZED Resultset with ColdFusion |
| 03/16/2011 | Using Metadata To Add Static Variables to ColdFusion Components |
| 03/10/2011 | Find an extension |