Do You PHP はてな このページをアンテナに追加 RSSフィード Twitter

2008-09-01

[][]Propelで作ったモデルのsaveメソッドとトランザクション

個人用メモ&symfony1.0.17での話。

Propel+Creoleで明示的にトランザクションを開始・終了するには

<?php
$con = Propel::getConnection();
try {
    $con->begin();
          :
    $con->commit();
} catch (Exception $e) {
    $con->rollback();
    throw $e;
}
}

な感じで、CreoleのConnection#begin、Connection#commit、Connection#rollbackを使えばOK(Connectionはインターフェース)。

で、Propelで作ったモデルのBaseクラスのsaveメソッドを見ると

<?php
    public function save($con = null)
    {try {
            $con->begin();
            $affectedRows = $this->doSave($con);
            $con->commit();
            return $affectedRows;
        } catch (PropelException $e) {
            $con->rollback();
            throw $e;
        }
    }

のように、ここでトランザクションをcommit/rollbackしているように見える(かなり焦った)。ここでコードを追ってみると、抽象クラスであるConnectionCommonクラスでbegin/commit/rollbackの実装がされている。で、

<?php
abstract class ConnectionCommon {public function commit() 
    {
        if ($this->transactionOpcount > 0) {
            if ($this->transactionOpcount == 1 || $this->supportsNestedTrans()) {
                $this->commitTrans();
            }
            $this->transactionOpcount--;       
        }
    }

な感じで、トランザクションのネストとネストしたトランザクションをサポートしてるかどうかをチェックしている。今使っているのはPostgreSQL8.3系でネストしたトランザクションはサポートされていないはずで、実際に使うPgSQLConnectionクラス(ConnectionCommonクラスのサブクラス)でもsupportsNestedTransメソッドでfalseが返るようになっている。

なので、

<?php
class Relevancy extends BaseRelevancy
{
    public function save($con = null) {
        $con = Propel::getConnection();
        try {
            $con->begin();
            $ret = parent::save($con);

            $answer = $this->getAnswer();
            if ($this->getScore() == 1) {
                $answer->setRelevancyUp($answer->getRelevancyUp() + 1);
            } else {
                $answer->setRelevancyDown($answer->getRelevancyDown() + 1);
            }
            $answer->save($con);

            $con->commit();

            return $ret;
        } catch (Exception $e) {
            $con->rollback();
            throw $e;
        }
    }
}

と書いてもフラット(?)な1つのトランザクションで管理される(ハズ)。

はじめましてはじめまして 2010/07/30 19:01 はじめまして、
http://www.phppro.jp/qa/2827
こちらにも投稿されましたが、ロールバックはきかず、コミットされてしまうようです。
※私のつたない調査だけなので未だなんとも分かりませんが・・。
とりあえずご一報させていただきます。

shimookashimooka 2010/08/01 19:22 saveメソッドを呼び出す際にConnectionオブジェクトを渡してみてください。
Connectionオブジェクトを渡さない場合、内部で、トランザクションを開始していない接続が新たに取得され使用されています。

オフィスアンツオフィスアンツ 2010/08/01 21:49 ありがとうございます。
$answer->save($con);
ということですね。
これは、なくてもトランザクション実現できるようです(すみません・・私の調査ですが)。

原因は、大変お恥ずかしいかぎりなのですが、
MyISAMにしておりました。
上記はInnoDBにしたら問題なくロールバックは効くようです。
・・・自信ないのですが、DBのコネクションはシングルトンにしてあると思いますので、いちいちコネクションを渡さなくても大丈夫なの(かな?)と思います。。
大変失礼しました。

shimookashimooka 2010/08/02 12:43 単一操作(登録/更新/削除)の場合は問題ないと思いますが、このエントリのように、複数操作を同一トランザクションとして扱いたい場合はConnectionオブジェクトを渡す必要があると思います。

オフィスアンツオフィスアンツ 2010/08/03 09:43 shimookaさま、なるほど、、ソースを読んではいませんがモデル上でおっしゃる通りのことをするときは渡すべきなのでしょうね・・。
諸々アドバイス有り難うございました。・・とっても参考になりました。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/shimooka/20080901/1220259885