̃Gg[͂ĂȃubN}[Nɒlj

DB(MySQL) :: transaction(トランザクション)を含むメソッドの再利用



トランザクションのネスト

MySQL において、トランザクションのネストは出来ません。
例えば以下のコードは動作しません。

<?php

/**
 * a_tbl{1,2} のトランザクション
 */
function execA(PDO $db)
{
    try {
        $db->beginTransaction();
        $db->exec('INSERT INTO a_tbl1 ... ');
        $db->exec('INSERT INTO a_tbl2 ... ');
        $db->commit();
    } catch (Exception $e) {
        $db->rollback();
        throw $e;
    }
}

/**
 * b_tbl{1,2} のトランザクション
 */
function execB(PDO $db)
{
    try {
        $db->beginTransaction();
        $db->exec('INSERT INTO b_tbl1 ... ');
        $db->exec('INSERT INTO b_tbl2 ... ');
        $db->commit();
    } catch (Exception $e) {
        $db->rollback();
        throw $e;
    }
}

// a_tbl{1,2}, b_tbl{1,2} のトランザクション
try {
    $db->beginTransaction();
    execA($db); // トランザクションのネスト(入れ子)
    execB($db); // トランザクションのネスト(入れ子)
    $db->commit();
} catch (Exception $e) {
    $db->rollback();
    throw $e;
}

 ⇒ エラー...

上記の execA, execB メソッドは、再利用性を高める為にわざわざ切り出しているのですが、内部で個別にトランザクションを行っているので、組み合わせて利用できません。
では、下記のように内部で行っているトランザクションを廃止すれば良いでしょうか。


トランザクションの廃止

<?php

/**
 * a_tbl{1,2} のトランザクションを廃止
 */
function execA(PDO $db)
{
    try {
        // $db->beginTransaction();
        $db->exec('INSERT INTO a_tbl1 ... ');
        $db->exec('INSERT INTO a_tbl2 ... ');
        // $db->commit();
    } catch (Exception $e) {
        // $db->rollback();
        throw $e;
    }
}

/**
 * b_tbl{1,2} のトランザクションを廃止
 */
function execB(PDO $db)
{
    try {
        // $db->beginTransaction();
        $db->exec('INSERT INTO b_tbl1 ... ');
        $db->exec('INSERT INTO b_tbl2 ... ');
        // $db->commit();
    } catch (Exception $e) {
        // $db->rollback();
        throw $e;
    }
}

// a_tbl{1,2}, b_tbl{1,2} のトランザクション
try {
    $db->beginTransaction();
    execA($db);
    execB($db);
    $db->commit();
} catch (Exception $e) {
    $db->rollback();
    throw $e;
}

 ⇒ 問題なし(エラーは発生しない)

しかし上記の場合、例えば execA メソッドを単独で利用する時でも下記のように呼び出し元でトランザクションを行う必要があります。

try {
    $db->beginTransaction();
    execA($db);
    $db->commit();
} catch (Exception $e) {
    $db->rollback();
    throw $e;
}

ケアレスミスで、トランザクション記述を忘れてしまうことがありそうなので、注意が必要です。
ではどうするべきでしょうか?
再利用性を高めつつ、ケアレスミスを防止する。
私は以下のように対応しています。


トランザクションを引数でスイッチする

トランザクションを行うか否かを boolean型の引数でコントロールします。
具体的には、下記 execA, execB メソッドに第二引数($tran)を実装します。

<?php

/**
 * a_tbl{1,2} のトランザクション
 * @param bool $tran true:トランザクションON / false:トランザクションOFF
 */
function execA(PDO $db, $tran = true)
{
    try {
        $tran && $db->beginTransaction();
        $db->exec('INSERT INTO a_tbl1 ... ');
        $db->exec('INSERT INTO a_tbl2 ... ');
        $tran && $db->commit();
    } catch (Exception $e) {
        $tran && $db->rollback();
        throw $e;
    }
}

/**
 * b_tbl{1,2} のトランザクション
 * @param bool $tran true:トランザクションON / false:トランザクションOFF
 */
function execB(PDO $db, $tran = true)
{
    try {
        $tran && $db->beginTransaction();
        $db->exec('INSERT INTO b_tbl1 ... ');
        $db->exec('INSERT INTO b_tbl2 ... ');
        $tran && $db->commit();
    } catch (Exception $e) {
        $tran && $db->rollback();
        throw $e;
    }
}

// a_tbl{1,2}, b_tbl{1,2} のトランザクション
try {
    $db->beginTransaction();
    execA($db, false); // false: 関数内のトランザクションを行わない
    execB($db, false); // false: 関数内のトランザクションを行わない
    $db->commit();
} catch (Exception $e) {
    $db->rollback();
    throw $e;
}

 ⇒ 問題なし(エラーは発生しない)
try {
    execA($db);
} catch (Exception $e) {
    throw $e;
}

もちろん上記でもトランザクションが保証されます。





programming/php/etc/reuse_transaction_method.txt