Class::DBIで複数データベースを扱う+register_cleanup
Class::DBIで同じ構造の複数データベース扱う時には、まかまかさんのClass::DBI::Plugin::MultiDatabases(後で知った)や、Class::DBIのWikiにあるけど、さらにmod_perl上でのTips
ちょっと実際書いたコードと違うので全くこの通りでうごくかどうか心配なのですが、複数データベースに接続をするモジュールを下のように書いてみる。
package Object;
use strict;
use DBI;
use base qw(Class::DBI);
use Class::DBI::Plugin::NoCache;
use MyConfig;#データベースの接続情報を返すモジュールとする
__PACKAGE__->mk_classdata('dbhandles',{});
__PACKAGE__->mk_classdata('dbh_key');
__PACKAGE__->nocache(1);
sub change_db{
my $class = shift;
return Object->dbh_key(@_);
}
sub db_Main{
my $proto = shift;
my $self = ref($proto) || $proto;
my $dbhandles = Object->dbhandles;
my $key = $self->dbh_key;
$key ||= "キーのデフォルト";
return $self->_croak("Could not get dbh_key") unless $key;
return $dbhandles->{$key} if($dbhandles->{$key});
#MyConfigはデータベースの接続情報を返す
my $confing=MyConfig->new($key)
or return $self->_croak("Could not get dbh config");
my $dbh = DBI->connect_cached(
"dbi:mysql:".$config->dsn.":".$config->dbhost,
$config->dbuser,$config->dbpass,
{$self->_default_attributes}
) or return $self->_croak("Could not connect to database");
$dbhandles->{$key}=$dbh;
Object->dbhandles($dbhandles);
return $dbh;
}
1;
Cacheのこともあるので、Class::DBI::Plugin::NoCacheも使用してます。
これでこのモジュールを継承して使用します
package Object::CD;
use strict;
use base qw(Object);
__PACKAGE__->table('cds');
__PACKAGE__->column(All => qw/cdid title artist/);
__PACKAGE__->has_many(tracks => 'Object::Track');
package main;
Object::CD->change_db('dbA');
my $cd = Object::CD->retrieve(123);
my $tracks = $cd->tracks;
Object::CD->change_db('dbB');
Object::CD->retrieve_all();
たぶん、これはまとも動いてくれると思います。has_many、has_aも正しく動くでしょう。
けどこれを、mod_perl環境でうごかすと、
最初のアクセスで、
Object::CD->change_db('dbA');
my $cd = Object::CD->retrieve(123);
my $tracks = $cd->tracks;
としてしまうと、以降のアクセスで、デフォルトのDBを使いたくても、
my $cd = Object::CD->retrieve(123); my $tracks = $cd->tracks;
としてしまったときに、デフォルトではなく、dbAが返ってくる(ことがある)ようになってしまいます。
dbh_keyをObjectモジュールのClassDataに保存してますから、グローバルな値として次のリクエストでも使われてしまうのです。
対策として、
Object::CD->change_db('デフォルトのキー');
を必ず入れる。例えば、CGI::Appのprerunなどに挿入しておけばOKです。もしくは、ApacheのCleanupHandlerを利用する手もあります。CleanupHandlerはリクエストのトランザクション終了後に呼び出される後処理Handlerで、register_cleanup()で登録をすることができます。これを使います。上記のchange_dbを以下のようにします。
sub change_db{
my $class = shift;
if(@_ > 0 && exists $ENV{MOD_PERL}){
#classdataのdbh_keyを保持してしまうので、cleanup handlerで消す。
#CGI.pmのソースが参考になる
require Apache;
my $r = Apache->request;
$r->register_cleanup(sub{
Object->dbh_key(0);
});
}
return Object->dbh_key(@_);
}
リクエストの処理終了後に、dbh_keyに「0」をいれることで、値をresetしています。
mod_perl2への対応等はCGI.pmのソースをみるのがよさそうです。CGI.pmもglobalな変数の片付けをregister_cleanupを利用して行ってました。
Class::DBIとTemplate-Toolkitでマジ生産性あがりました。Apacheのプロセスのメモリサイズも増えそうだけど。バナーの管理ページ7つ分。10日間で完了。