Teng でトランザクション処理 & はまった点

前回は、Teng で一通りの CRUD の仕方を紹介したんですが、トランザクション処理を考慮していなかったので、Teng でトランザクション処理をする方法を紹介します。

準備

テーブルは前回使用した memo テーブルを使います。
事前に以下の様なデータを入れておきました。

mysql> select * from memo order by id;
+----+-------+---------+---------------------+---------------------+
| id | title | body    | created_at          | updated_at          |
+----+-------+---------+---------------------+---------------------+
|  1 | Hello | World   | 2011-11-24 23:05:56 | 2011-11-24 23:05:56 |
|  2 | red   | green   | 2011-11-24 23:05:56 | 2011-11-24 23:05:56 |
|  3 | black | white   | 2011-11-24 23:05:56 | 2011-11-24 23:05:56 |
|  4 | foo   | bar baz | 2011-11-24 23:05:56 | 2011-11-24 23:05:56 |
+----+-------+---------+---------------------+---------------------+

トランザクション処理に使用するメソッド

以下のメソッドを使用して、トランザクション処理を行います。

Teng#txn_begin
トランザクションを開始
Teng#txn_commit
処理を確定
Teng#txn_rollback
処理の取り消し

サンプルコード

前回紹介した、データの更新にトランザクション処理をするための処理を追加してあります。

use strict;
use warnings;
use feature qw( say );
use lib "lib";

use My::DB;
use DateTime;
use Data::Dumper;

my $teng = My::DB->new(connect_info => [
    'dbi:mysql:database=testdb',
    'test',
    'tes10',
    +{ RaiseError => 1, PrintError => 0, AutoCommit => 1, },
]);

$teng->txn_begin;

my $iter = $teng->search(memo => +{ id => [1, 2] });
while (my $row = $iter->next) {
    $row->update({ body => 'transaction test' });
}

$teng->txn_commit;

say $teng->single(memo => +{ id => 1 })->body;
say $teng->single(memo => +{ id => 2 })->body;

$teng->txn_begin;$teng->txn_commit; を追加したのみです。

実行結果です。

transaction test
transaction test

これでうまく動いているのですが、実ははまった箇所があるのでその説明です。

AutoCommit off 設定でエラー

トランザクション処理をするということで、AutoCommit を off に。これがはまる原因で、最初はインスタンスの生成を以下の様なコードにしていました。

my $teng = My::DB->new(connect_info => [
    'dbi:mysql:database=testdb',
    'test',
    'tes10',
    +{ RaiseError => 1, PrintError => 0, AutoCommit => 0, },
]);

そして、実行してみると以下の様なエラーが出ます。

DBD::mysql::db begin_work failed: Already in a transaction at /home/tanida/perl5/perlbrew/perls/perl-5.12.1/lib/site_perl/5.12.1/DBIx/TransactionManager.pm line 32.

begin_work という見慣れないメソッドが出てきたので、DBI のドキュメントを読んでみたところ、AutoCommit を off にするメソッドでした。さらに読み進めると、AutoCommit が off の状態で呼び出すとエラーになるという記述が。

これが原因だと考え、以下の簡単なコードを書いて確認。

use strict;
use warnings;
use feature qw( say );
use DBI;
use Data::Dumper;

my $dbh = DBI->connect('dbi:mysql:database=testdb', 'test', 'tes10', +{
     RaiseError => 1, PrintError => 0, AutoCommit => 0,
});

$dbh->begin_work;
$dbh->do(
    qq{update memo set body = ? where id in (?, ?)},
    undef,
    'hogehoge', 1, 2
);

$dbh->commit;
$dbh->disconnect if $dbh;

実行結果です。

DBD::mysql::db begin_work failed: Already in a transaction at begin_work_test.pl line 11.

期待したとおりエラーになってくれました。

Teng#txn_begin の内部で呼び出している、DBIx::TransactionManager#txn_begin で DBI#begin_work を呼び出しているので、AutoCommit は on にする必要がありそうです。(調べが足りてないだけかもなので、もう少し調査します)

最後に

DBI#begin_work という知らないメソッドが出てきたりしたので、DBI そのものもまだまだ知らないことが多くありそうだなと感じました。DBI 自体もいじくり倒してみたいと思います。