この文書はClass::DBIの初期バージョンのドキュメントを訳したもので、現在のバージョンとは整合性がありません。CPAN から最新のバージョン を取得して内容を確認してください。
Class::DBI - オブジェクトのシンプルな永続
package Film; use base qw(Class::DBI);
# Class::DBI にあなたのクラスのことを教えてあげる
Film->table('Movies');
Film->columns(All => qw( Title Director Rating NumExplodingSheep ));
Film->columns(Primary => qw( Title ));
Film->set_db('Main', 'dbi:mysql', 'me', 'noneofyourgoddamnedbusiness',
{AutoCommit => 1});
#-- 一方、スクリプトの方で! --# use Film;
# "Bad Taste" 用の映画エントリを新しく作る
$btaste = Film->new({ Title => 'Bad Taste',
Director => 'Peter Jackson',
Rating => 'R',
NumExplodingSheep => 1
});
# 'Gone With The Wind' のエントリをデータベースから取得する
my $gone = Film->retrieve('Gone With The Wind');
# なんと新たな場面が見つかって、Scarlet と羊の猥褻シーンだった!
$gone->NumExplodingSheep(5);
$gone->Rating('NC-17');
$gone->commit;
# 'Bladerunner' エントリを取り出す
my $blrunner = Film->retrieve('Bladerunner');
# 'Bladerunner' のコピーをつくって、ディレクターズ・カットのエントリをつくる
my $blrunner_dc = $blrunner->copy("Bladerunner: Director's Cut");
# Ishtar はもうなくてもいい
Film->retrieve('Ishtar')->delete;
# PG のレイティングを持つ映画を全部探す
@films = Film->search('Rating', 'PG');
# Bob が監督をした映画を全部探す
@films = Film->search_like('Director', 'Bob %');
わたしはSQLが嫌い。あなたもSQLが嫌い。みんなSQLが嫌い。でも、悲しいかな、 オブジェクトを永続させるニーズはしょっちゅうあります。そんなとき、たいがいSQL データベースがもっとも柔軟なソリューションになります。
このモジュールは、少しのSQLとDBIの知識で、効率的で、シンプルで、拡張性の高い 永続オブジェクトをセットアップするためのものです。
スキーマを用いて、クラスのデータへのアクセサを自動的に提供します。 これらのアクセサによって、データベースへのアクセスを制御します。
あなたのクラスを永続化させるとても簡単な方法を示します。 個々のメソッドについての詳細は、あとで。
データベースをセットアップする。
困った時は、DBD::CSV でも大丈夫です。
オブジェクトを格納するテーブルをセットアップする。
さきほどの映画の例でいうと、こんな感じのテーブルを作成する必要があります。
CREATE TABLE Movies (
Title VARCHAR(255) PRIMARY KEY,
Director VARCHAR(80),
Rating CHAR(5), /* すくなくとも 'NC-17' にフィットする */
NumExplodingSheep INTEGER
)
Class::DBI を継承する。
package Film; use base qw(Class::DBI);
カラムを宣言する。
Film->columns(All => qw( Title Director Rating NumExplodingSheep ));
カラム宣言を効率的に行う方法をもっと知りたければ、"カラムのlazyな生息" をみてください。
テーブル名を宣言する。
Film->table('Movies');
どのフィールドが Primary Key かを宣言する。
Film->columns(Primary => 'Title');
データベース接続を宣言する。
Film->set_db('Main', 'dbi:mysql', 'user', 'password', {AutoCommit => 1});
set_db() は Ima::DBI から継承されています。詳細はモジュールの man page をみてください。
XXX これはもうちょっとシンプルにすべきでしょう。set_db_main() みたいにするとか。
完了。
すばらしいでしょう? このモジュールを崇拝して下さい。
以下に示すメソッドは、オブジェクトのデータ構造が ハッシュもしくは疑似ハッシュを使っている、という前提を使用しています。
以下は、格納されるオブジェクトを生成、取得、消去するためのメソッドです。 これでなんでもできる、というわけではないので、オーバーライドする必要があるかも知れません。
new
$obj = Class->new(\%data);
$obj はハッシュリファレンスからつくられる、Class のインスタンスです。
# "Bad Taste" 用の映画エントリをつくる
$btaste = Film->new({ Title => 'Bad Taste',
Director => 'Peter Jackson',
Rating => 'R',
NumExplodingSheep => 1
});
もし PRIMARY KEY のカラムが%data にない場合、new() は、そのカラムのデータは生成される、と想定します。 sequence() がこのクラスに対して指定されていれば、それを使います。 そうでなければ、PRIMARY KEY は AUTO_INCREMENT 制約があるのだと想定し、それを使おうとします。
クラスが外部クラスに対して hasa() で関係を定義されている場合、 new() に そのキーの値としてオブジェクトを渡すことができます。 Class::DBI はちゃんとまっとうに扱います。
retrieve
$obj = Class->retrieve($id);
my $gone = Film->retrieve('Gone With The Wind');
copy
$new_obj = $obj->copy($new_id);
my $blrunner_dc = $blrunner->copy("Bladerunner: Director's Cut");
delete
$obj->delete;
Class::DBI は Class::Accessor を継承しているので、 あなたのつくったサブクラスに対して、全てのカラムへのアクセサメソッドを 提供します。アクセサが提供する get() と set() のメソッドをオーバーライドして、 データベースのトランザクションを自動的に制御できるようにしています。
アクセサの動きには2つのモードがあります。手動コミットとオートコミットです。 DBI の手動/オートコミットに似ているのですが、それと同じように実装されているわけではありません。 簡単に言うと ... オートコミットモードでは、変更するためのアクセサがよびだされるたび、 その変更はただちにデータベースに書き込まれます。 そうでない場合、つまりオートコミットが off の場合、commit() が明示的に呼ばれるまで、変更はデータベースには書き込まれません。
手動コミットの例はこちらです。
# NumExplodingSheep() と Rating() は メモリ上で
# データ変更するだけで、データベースには書き込まれない。
# commit() が呼ばれると、データベースに1度に書き込まれる。
$gone->NumExplodingSheep(5);
$gone->Rating('NC-17');
$gone->commit;
こちらはオートコミットの例。
# このオブジェクトについては、オートコミットを on にする。
$gone->autocommit(1);
# それぞれのアクセサ呼び出しによって、新しい値がすぐに書き込まれる
$gone->NumExplodingSheep(5);
$gone->Rating('NC-17');
手動コミットのほうが、たぶんオートコミットよりも効率的でしょうし、 rollback() によって、変更を取り消す安全も提供されます。 オートコミットはプログラマにとっては楽でしょう。
オブジェクトが破棄されたとき(スコープ外にでるか、プログラムが終了した場合)に、 変更がコミットされず、ロールバックもされていない場合、 Class::DBI の DESTROY メソッドが呼び出されて、変更が保存されてないことについて warning を表示します。
autocommit
Class->autocommit($on_or_off);
$commit_style = Class->autocommit;
$obj->autocommit($on_or_off);
$commit_style = $obj->autocommit;
Class->autocommit(1); # オートコミットはクラスに対して on
$obj = Class->retrieve('Aliens Cut My Hair');
$obj->autocommit(0); # このオブジェクトについてはオートコミット off
オブジェクトごとのコミットの設定はデータベースには格納されません。
オートコミットはデフォルト off です。
注意 これは DBI の AutoCommit 属性とは 何の関係もありません。
commit
$obj->commit;
rollback
$obj->rollback;
もしオートコミットの状態でこのメソッドを呼び出すと、例外を投げます。
is_changed
@changed_keys = $obj->is_changed;
id
$id = $obj->id;
table
Class->table($table); $table = Class->table; $table = $obj->table;
テーブル情報はサブクラスに継承されますが、オーバーライドはできません。
sequence
Class->sequence($sequence_name); $sequence_name = Class->sequence; $sequence_name = $obj->sequence;
Class->columns(Primary => 'id');
Class->sequence('class_id_seq');
Class::DBI は、オブジェクトが生成されたが、PRIMARY KEY が指定されていない場合、シーケンスを利用して PRIMARY KEY を作ろうとします。
注意: Class::DBI は AUTO_INCREMENT や、それに類するセマンティクスもサポートしています。
columns
@all_columns = $obj->columns; @columns = $obj->columns($group); Class->columns($group, @columns);
カラムは特有の使い方によってグループわけすることができ、それによってグループ内のカラムを一度に公理強く読み込むことができる。 これについてのもっと詳しい情報は、"カラムのlazyな生息"をみてください。
「予約された」3つのグループがあります。'All', 'Essential' そして 'Primary' です。
'All' はクラスでつかわれるすべてのカラムです。 セットされない場合、他のグループから自動生成されます。
'Primary' はクラスの単一PRIMARY KEY です。オブジェクトを使用する前に、必ずセットされなければなりません。 (複数のPRIMARY KEYも、最終的にはサポートされることになるでしょう)
Class->columns('Primary', 'Title');
'Essential' はオブジェクトをロードし、使用するのに最低限必要なカラムの集合です。
オブジェクトが retrieve() されると、このグループ内のカラムのみがロードされます。
あるクラスが、たくさんのカラムをもっているが、たいがいの場合その中のいくつかしか使わないとき、メモリ使用量を削減するといったときに使います。
もしセットしなければ、Class->columns('All') から自動生成されます。
引数を与えない場合、すべてのカラムのリストがほしいものと想定されます。
注意 スカラコンテキストでの動作をどうするか、まだ決めていません。
is_column
Class->is_column($column);
$obj->is_column($column);
ひとつのテーブルが別のテーブルを FOREIGN KEY で参照するように、 あるオブジェクトに別のオブジェクトを持たせたいことはよくあるでしょう。 たとえば、映画監督について、もっとたくさん情報を持たせたいとき。 テーブルをつくります...
CREATE TABLE Directors (
Name VARCHAR(80),
Birthday INTEGER,
IsInsane BOOLEAN
)
そして、Class::DBI のサブクラスを用意します。
package Film::Directors;
use base qw(Class::DBI);
Film::Directors->table('Directors');
Film::Directors->columns(All => qw( Name Birthday IsInsane ));
Film::Directors->columns(Prmary => qw( Name ));
Film::Directors->set_db(Main => 'dbi:mysql', 'me', 'heywoodjablowme',
{AutoCommit => 1});
これで Film は カラム Director を通して、監督名だけでなく、Film::Directors のオブジェクトを取得できるようになります。 Film に1行追加するだけでできます。
# Director() は Film::Directors オブジェクトへのアクセサ
Film->hasa('Film::Directors', 'Director');
これで Film->Director() アクセサは、監督名の代わりに、Film::Director オブジェクトを get/set できるようになります。
hasa
Class->hasa($foreign_class, @foreign_key_columns);
@foreign_key_columns の先頭要素の名前で アクセサが生成されます。これによって、$foreign_class のオブジェクトを get/set できます。 Film::Director の例で言うと ...
# Bad Taste の監督を Peter Jackson を表す
# Film::Director オブジェクトにする
$pj = Film::Directory->retreive('Peter Jackson');
$btaste = Film->retreive('Bad Taste');
$btaste->Director($pj);
hasa() は外部クラスの require を行おうとします。 もし require が失敗しても、単純な require ではない (つまり Foreign::Class は Foreign/Class.pm ではない) と判断し、すでにあなたがそのあたりを処理しているものとして、warning を無視します。
@foreign_key_columns をセットアップするのに、columns() を呼び必要はありません。 やっていなければ、hasa() が自動的にやってくれます。
XXX この動作が気持ちいいかどうか、疑問です。将来すこし変わるかもしれません。 アクセサの命名形式があまり自信がないです。
注意 2つのクラスは同じデータベースに存在する必要はありません!
Perl の伝統にのっとって、Class::DBI はオブジェクトのロードについてlazy です。 たくさんのオブジェクトを同時に扱うときなど、必要なカラムは少ししかないのに、全部のカラムを取り出すのはメモリの無駄だなあ、と思うときがあるでしょう。
Class::DBI はカラムをグループでとりだします。グループ内の1つのカラムにアクセスすると、グループの他のカラムも使用するだろうと想定して、グループのカラムを全部ロードします。 よって、たとえば Film クラスに NetProfit(純利益) と GrossProfit(総利益) のカラムを追加したいとしましょう。 おそらく、この2つはいつも一緒に利用されるでしょう。ですから ...
Film->columns('Profit', qw(NetProfit GrossProfit));
これで、次のように呼び出すと、
$net = $film->NetProfit;
Class::DBI は NetProfit と GrossProfit 両方をデータベースからロードします。 次に GrossProfit() を同じオブジェクトに対して呼び出した場合、データベースを叩く必要はありません。 これによってパフォーマンスを上げることができるかもしれません (YMMV)。
この動作が気に食わないならば、'All' というグループだけつくって、カラムをすべてそこに入れて下さい。そうすれば、Class::DBI はすべて一度にロードします。
SQL はたいがい、case insensitive (大文字小文字の違いは無視する) です。 Perl はたいがい、そうではありません。これによって、データベースから情報を取り出すときに問題が起こることがあります。 Class::DBI はデータのちょっとした正規化をおこないます。
normalize
$obj->normalize(\@columns);
normalize_hash
$obj->normalize_hash(\%hash);
Class::DBI は Ima::DBI を継承しているので、Ima::DBI スタイルでデータベースやDBIを扱うのが好ましいです。(Ima::DBI の man page を流し読みしてみましょう)
サブクラスによって継承することができるようなメソッドを新しく書くためには、クラスのテーブル名、PRIMARY KEY 名をハードコードしないように注意しなければなりません。 set_sql() を使うと、効率的に、キャッシュされるステートメント・ハンドラを生成することができます。
一般に、set_sql() の呼び出しはこんな感じになります。
# sql_GetFooBar() を定義する
Class->set_sql('GetFooBar', <<'SQL');
SELECT %s
FROM %s
WHERE Foo = ? AND Bar = ?
これによって、 sql_GetFooBar() というメソッドが定義されます。 引数は sprintf() を通して、SQL文を埋めるのに使われます。
my $sth = Class->sql_GetFooBar(join(', ', Class->columns('Essential')),
Class->table);
クラスのテーブル名やPRIMARY KEY名をハードコードしないように注意して下さい。代わりに、table() や columns() メソッドを使います。
$db_name が省略された場合、'Main'接続 を使用すると想定します。
いくつかのシンプルな検索用メソッドが提供されています。シリアスな検索ではなく、クラスの可能性を表示するものです。
search
@objs = Class->search($key, $value); @objs = $obj->search($key, $value);
@films = Film->search('Rating', 'PG');
search_like
@objs = Class->search_like($key, $like_pattern); @objs = $obj->search_like($key, $like_pattern);
XXX % と _ のかわりに、* と ? を使ったグロブスタイル版もつくるべき?
# Bob という名前の人が監督した映画をさがす
@films = Film->search_like('Director', 'Bob %');
うーん... まあ、概要があるし。
XXX もっと例が必要ですね。そのうち埋まるでしょう。
SQLってのは困ったもので、リストを格納するのは複雑で、ハッシュを格納するのは、1個テーブルが必要になります。 これ以上複雑なことはまだはじめないようにしてください。もしリストを格納したければ、 自分自身でアクセサを書いてください(これの処理方法はそのうち実験するつもりです)。 もしハッシュを格納したければ、もう1つテーブルとクラスを作ることを考慮した方がいいでしょう。
だれかに説得されて、自動的にデータをシリアライズするアクセサをつくるかもしれません。
すべてのクラスに1つテーブルを定義して下さい。2個以上のテーブルにひとつのクラスが 分散することはできません。これを扱えるようにするのは、かなり頭が痛い話です。
最終的には、これらのテーブルのリンクや、リストのデータを表すテーブルに関する制限を外すことになるでしょう。
SQLテーブルに、2個以上 PRIMARY KEY を持たせるのは、現状サポートされていません。なぜって? 複雑だからです。将来のバージョンで、複数のキーをサポートします。
2つのテーブル/オブジェクトの関係を扱ううまい方法がありません。 最終的には、とても単純な方法で、これらの関係をサポートしていくつもりです。
もののリストをオブジェクトデータとして扱ううまい方法がありません。 これもまた、最終的には実装しようと考えているもののひとつです。
もしこの機能が必要ならば、教えて下さい。動くようにします。
$obj->commit は DBI->commit にすべき???
クラスワイドなコミットとロールバックの、簡単な方法が必要。
DBD::mysql - MySQL 3.22 and 3.23
DBD::RAM
Michael G Schwern <schwern@pobox.com> Uri Gutman, Damian Conway, Mike Lambert そして POOP グループに、たくさん 深夜にわたっての助けをもらいました。
Ima::DBI, Class::Accessor, base, Class::Data::Inheritable http://www.pobox.com/~schwern/papers/Class-DBI/, Perl Object-Oriented Persistence <poop-group@lists.sourceforge.net>, Alzaboそして Tangram