トランザクションについてB!

こんにちわ! Yappo です!

2十日目は Data::Model でのトランザクションの使い方です。

トランザクションについての説明は省きます。

http://e-words.jp/w/E38388E383A9E383B3E382B6E382AFE382B7E383A7E383B3.html

このあたりを読めばいいんじゃないでしょうか。

早速試しましょう。

ちなみに実験するときはトランザクションに対応しているRDBMSを使ってください。

MySQL の場合だと MyISAM でためしても MyISAM がトランザクションに対応していないのでいみがありません。

InnoDBで試しましょう。

なお、 install_model にて CREATE TABLE 文を作るときのテーブルオプションを与える事で TYPE=InnoDB のような CREATE TABLE SQL が作成出来ます。

    install_model txn_test => schema {
        key 'id';
        columns qw( id name );
        schema_options create_sql_attributes => {
            mysql => 'TYPE=InnoDB',
        };
    };

のようにして schema_options を設定します。

create_sql_attributes は CRATE TABLE NAME (); の attribute のとこに任意の SQL を追加できるというオプションで、任意の SQL を HASH リファレンスの中に書きます。

HASH リファレンスの key は DBD ドライバ名で mysql や SQLite などが指定でき、 value に実際追加したい SQL を書きます。

上記の定義を as_sqls すると下記のようになります。

CREATE TABLE txn_test (
    id              CHAR(255)      ,
    name            CHAR(255)      ,
    PRIMARY KEY (id)
) TYPE=InnoDB;

使ってみる

# トランザクションの開始
my $txn = $db->txn_scope;

# INSERT
$txn->set(
    txn_test => 1 => { name => 'Yappo' }
);

# トランザクションを終了してデータを commit する!
$txn->commit;

このように書くと、 txn_scope メソッドを呼び出したときに帰ってくるオブジェクトを保持しているあいだ

トランザクションが有効となります。

なので

# トランザクションの開始
my $txn = $db->txn_scope;

# LOOKUP
my $row = $txn->lookup( txn_test => 1 );

# data の update
$txn->update($row);

die 'oooops';

# トランザクションを終了してデータを commit する!
$txn->commit;

set/update/delete メソッドの後の処理が die などで異常終了した場合、

set/update/delete メソッドでの更新が rollback されます。

もっと詳しくいうと、 $txn のスコープが外れた時に $txn->commit されていなければ強制的に rollback されます。

これは DBIx::Class::Storage::TxnScopeGuard とほとんど同じ挙動です。

通常との違い

上記の例では、 $db->set を使わずに $txn->set などを使って INSERT していました。

これは、トランザクションスコープ内では直接レコードの更新を許さないという設計によるものです。

START TRANSACTION してる間に他のメソッドを呼びだして、そのメソッドの中で意図しない更新クエリが走った時に抑制してくれたりします。

実際にトランザクションスコープ中に通常の方法で更新系を呼び出すと、下記のようにエラーになります。

my $txn = $db->txn_scope;
eval {
    $db->set(
        txn_test => 1 => { name => 'Yappo' }
    );
};
warn $@ if $@;# say "The 'set' method can not be performed during a transaction."

eval {
    $row->delete;
};
warn $@ if $@;# say "The 'delete' method can not be performed during a transaction."

これは、 lookup などして取得した Row オブジェクトなどに生えている update, delete メソッドに対しても有効となっています。

なので、更新系は txn_scope が返すインスタンス経由で実行する必要があります。

Row オブジェクトの update, delete メソッドは $txn->update($row) などとして txn_scope が返すインスタンスのメソッド経由で更新します。

# INSERT
$txn->set(
    txn_test => 1 => { name => 'Yappo' }
);

# LOOKUP
my $row = $txn->lookup( txn_test => 1 );
warn $row->name;

# UPDATE
$row->name('nekokak');
$txn->update($row);

# GET
my $itr = $txn->get('txn_test');
while (<$itr>) {
    warn $_->name;
}

# delete
$txn->delete($row);

$txn->commit;

まとめ

トランザクションをうまく使うとデータの一貫性を保証した処理を書くことができますので

びしばしつかってみてください。

have a nice data-model days!:)