CodeIgniterでは、DBへの登録・更新などを$this->db->trans_start();$this->db->trans_complete();で囲むことでトランザクションを張ることができますが、この機能は内部でネストの深さをカウンタで記録しているため、ネスト化して使用することが可能です(CodeIgniter2, CodeIgniter3で確認)。

通常のトランザクションを用いる関数例

例えば、単純な送金システムを設計する際、CustomerというModelに送金先へ残高を移動する以下のような関数を作成するとします。

public function execute_payment($source_id, $dest_id, $price) {
    // トランザクションを開始
    $this->db->trans_start();

    // 送金元から残高を引く
    $this->db->set('balance', 'balance-'.intval($price), false);
    $this->db->where('id', $source_id);
    $this->db->update('customers');

    // 送金先へ残高を加算
    $this->db->set('balance', 'balance+'.intval($price), false);
    $this->db->where('id', $dest_id);
    $this->db->update('customers');

    // トランザクションを終了
    return $this->db->trans_complete();
}

この例では、トランザクションを張っていることによって、残高を引く処理・加算する処理のいずれかでエラーが出た場合にトランザクション開始時の状態へロールバックされるようになっています。

これにより、片方だけ実行される(残高が引かれたのに送金されていない)という事態を防ぐことができ、データの整合性が保たれます。

ネスト化されたトランザクションを利用する例

上で挙げた関数では$this->db->trans_start();$this->db->trans_complete();を利用していますが、これを含む関数を複数個トランザクションの中に入れたい場合があります。その場合も、特に気にせず$this->db->trans_start();$this->db->trans_complete();で囲めば良いだけです。

すなわち、以下のように記述しても問題ありません。

$this->db->trans_start();

/* 何かしらのDB操作① */

$this->db->trans_start();

/* 何かしらのDB操作② */

$this->db->trans_complete();

/* 何かしらのDB操作③ */

$this->db->trans_complete();

このように記述した場合でも①~③すべてのDB操作が正常に実行されたときのみDBの状態が更新されます、どれか1つでもエラーが起きた場合は①~③の変更はすべてロールバックされます。

例えば、複数個の送金がある場合でも、以下のように書けばそのすべてでエラーが起きなかった場合のみDB更新が反映されます。

// トランザクションを開始
$this->db->trans_start();

// 複数件の送金処理
$this->customer->execute_payment(1, 2, 100); // 1番口座から2番口座へ100円移動
$this->customer->execute_payment(1, 3, 100); // 1番口座から3番口座へ100円移動
$this->customer->execute_payment(1, 4, 100); // 1番口座から4番口座へ100円移動

// トランザクションを終了
$this->db->trans_complete();

この例では、トランザクションを張ることにより3件の送金処理が全件成功した場合のみDBが更新されるようになっています。

なお、$this->db->trans_start();$this->db->trans_complete();は必ずしもDB操作を行うModel内に記述する必要はなく、別途Customerモデルにトランザクションの開始・終了のみを行うメソッドを実装するといった必要はありません。

ただし、Controller内で$this->dbを利用する際には、必要に応じて$this->load->database();をコンストラクタまたは各メソッドで呼び出すようにしてください。

 

この投稿をシェアする