以前
Catalyst::Model::DBIC::Plainでのトランザクション管理
というエントリーを書きました。
Catalyst::Model::DBIC::Plainってあたりが時代を感じさせます。
Cookbokにもありますが現在のDBIx::Classでトランザクションを管理する場合には$schema->txn_do($coderef)を使うとトランザクションの開始からコミット、ロールバックまでやってくれます。
my $schema = CD::Schema->connect(...);
$txn = sub {
my $artist = $schema->resultset('Artists')->create(
{
name => $name,
}
);
my $album = $schema->resultset('Albums')->create(
{
title => $title,
artist => $artist->id,
}
);
}
eval { $schema->txn_do($txn); };
if ($@) {
if ($@ =~ /Rollback failed/) {
die "rollback failed: $@"
}
else {
die "rollbacked: $@"
}
}
Catalyst::Model::DBIC::Schemaで使うときには
sub do_add : Local {
my ( $self, $c ) = @_;
...
my $txn = sub {
my $artist = $c->model('CD::Artists')->create(
{
name => $c->req->param('name'),
}
);
my $album = $c->model('CD::Albums')->create(
{
title => $c->req->param('title'),
artist => $artist->id
}
);
};
$c->do_txn($c->model('CD'), $txn );
eval { $c->model('CD')->schema->txn_do($txn); };
if ($@) {
if ( $@ =~ /rollback failed/ ) {
$c->error("rollback failed: $@");
}
else {
$c->error("rollbacked: $@");
}
}
...
}
これを毎回やるのは面倒です。
で、MLを見てたらMST曰く
[Catalyst] Best practice for using transactions?
You could always subclass Catalyst::Controller's _DISPATCH private
action like
sub _DISPATCH {
my $self = shift;
my ($c) = @_;
$c->model('DB')->schema->txn_do(
sub {
$self->next::method(@_);
}
);
}
つまり_DISPATCHメソッドをオーバーライドしたMyApp::Base::Controllerとか継承して全てのディスパッチに対してトランザクションを組んじゃえ・・・というとっても豪快な解がありました。
ボクの場合は局所的に使いたいのでプラグインを作ってこんな感じにしてます。
package MyApp::Plugin::Utils;
use strict;
use warnings;
sub do_txn {
my ( $c, $model, $txn ) = @_;
eval { $model->schema->txn_do($txn); };
if ($@) {
if ( $@ =~ /rollback failed/ ) {
$c->error("rollback failed: $@");
}
else {
$c->error("rollbacked: $@");
}
}
}
で、MyApp.pmでロード
package MyApp;
use strict;
use warnings;
use Catalyst::Runtime '5.70';
use Catalyst qw/
-Debug
Dumper
StackTrace
ConfigLoader
Static::Simple
+MyApp::Plugin::Utils
/;
...
で、コントローラでこんな感じ
sub do_add : Local {
my ( $self, $c ) = @_;
...
my $txn = sub {
my $artist = $c->model('CD::Artists')->create(
{
name => $c->req->param('name'),
}
);
my $album = $c->model('CD::Albums')->create(
{
title => $c->req->param('title'),
artist => $artist->id
}
);
};
$c->do_txn($c->model('CD'), $txn );
...
}
・・・あまり短くなってない orz
・・・けどエラー処理は一箇所にまとめられます。
もちろんInnoDBなどのトランザクション対応なデータベースエンジンを使わなければ意味はありません。