How to use nested DB transactions (MySQL 5+, PostgreSQL)

You are viewing revision #4 of this wiki article.
This version may not be up to date with the latest version.
You may want to view the differences to the latest version or see the changes made in this revision.

« previous (#3)next (#5) »

Source code taken from: PHP, PDO & Nested Transactions.

Tested with: MySQL 5.1.30 + Yii 1.1.8.

Problem: Say there is service layer in an application, and one service may use others. No each service deals will complex business logic will needs to wrap that into transactions.

If there are services A and B here's how it might happen:

class ServiceA {
    public function transA() {
        $trans = Yii::app()->db->beginTransaction();
        // code
        Yii::app()->serviceB->transB();
        // code
        // exception handling and stuff
        $trans->commit();
    }
}

class ServiceB {
    public function transB() {
        $trans = Yii::app()->db->beginTransaction();
        // code
        // exception handling and stuff
        $trans->commit();
    }
}

With default Yii implementation, PDO will throw exception saying there already is active transaction.

But there's a solution :)

First, you'll need to extend PDO class and save it in your the protected/components directory:

class NestedPDO extends PDO {
    // Database drivers that support SAVEPOINTs.
    protected static $savepointTransactions = array("pgsql", "mysql");

    // The current transaction level.
    protected $transLevel = 0;

    protected function nestable() {
        return in_array($this->getAttribute(PDO::ATTR_DRIVER_NAME),
                        self::$savepointTransactions);
    }

    public function beginTransaction() {
        if(!$this->nestable() || $this->transLevel == 0) {
            parent::beginTransaction();
        } else {
            $this->exec("SAVEPOINT LEVEL{$this->transLevel}");
        }

        $this->transLevel++;
    }

    public function commit() {
        $this->transLevel--;

        if(!$this->nestable() || $this->transLevel == 0) {
            parent::commit();
        } else {
            $this->exec("RELEASE SAVEPOINT LEVEL{$this->transLevel}");
        }
    }

    public function rollBack() {
        $this->transLevel--;

        if(!$this->nestable() || $this->transLevel == 0) {
            parent::rollBack();
        } else {
            $this->exec("ROLLBACK TO SAVEPOINT LEVEL{$this->transLevel}");
        }
    }
}

Now you can use it in db configuration array in protected/config/main.php:

'db'=>array(
     'pdoClass' => 'NestedPDO',
     'connectionString' => ...
),

That's it, there you go ;)

41 0
26 followers
Viewed: 66 538 times
Version: Unknown (update)
Category: How-tos
Written by: mindeh
Last updated by: Boaz
Created on: Jun 30, 2009
Last updated: 8 years ago
Update Article

Revisions

View all history

Related Articles