根据@RandomSeed 的回答,我为默认的 Yii 事务处理创建了一个“插入”:
$connection = Yii::app()->db;
$transaction=$connection->beginTransaction();
try
{
$connection->createCommand($sql1)->execute();
$connection->createCommand($sql2)->execute();
//.... other SQL executions
$transaction->commit();
}
catch(Exception $e)
{
$transaction->rollback();
}
这是我的 SingleTransactionManager 类:
class SingleTransactionManager extends CComponent
{
// The current transaction level.
private $transLevel = 0;
// The CDbConnection object that should be wrapped
public $dbConnection;
public function init()
{
if($this->dbConnection===null)
throw new Exception('Property `dbConnection` must be set.');
$this->dbConnection=$this->evaluateExpression($this->dbConnection);
}
// We only start a transaction if we're the first doing so
public function beginTransaction() {
if($this->transLevel == 0) {
$transaction = parent::beginTransaction();
} else {
$transaction = new SingleTransactionManager_Transaction($this->dbConnection, false);
}
// always increase transaction level:
$this->transLevel++;
return $transaction;
}
public function __call($name, $parameters)
{
return call_user_func_array(array($this->dbConnection, $name), $parameters);
}
}
class SingleTransactionManager_Transaction extends CDbTransaction
{
// boolean, whether this instance 'really' started the transaction
private $_startedTransaction;
public function __construct(CDbConnection $connection, $startedTransaction = false)
{
$this->_startedTransaction = $startedTransaction;
parent::__construct($connection);
$this->setActive($startedTransaction);
}
// We only commit a transaction if we've started the transaction
public function commit() {
if($this->_startedTransaction)
parent::commit();
}
// We only rollback a transaction if we've started the transaction
// else throw an Exception to revert parent transactions/take adquate action
public function rollback() {
if($this->_startedTransaction)
parent::rollback();
else
throw new Exception('Child transaction rolled back!');
}
}
此类“包装”主数据库连接,您应该在配置中将其声明为这样的组件:
'components'=>array(
// database
'db'=>array(
'class' => 'CDbConnection',
// using mysql
'connectionString'=>'....',
'username'=>'...',
'password'=>'....',
),
// database
'singleTransaction'=>array(
'class' => 'pathToComponents.db.SingleTransactionManager',
'dbConnection' => 'Yii::app()->db'
)
请注意,该dbConnection
属性应该是主数据库连接的表达式。现在,当在嵌套的 try catch 块中嵌套事务时,您可以在嵌套事务 3 中创建错误,并且 1 和 2 上的错误也会回滚。
测试代码:
$connection = Yii::app()->singleTransaction;
$connection->createCommand('CREATE TABLE IF NOT EXISTS `test_transactions` (
`number` int(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;')->execute();
$connection->createCommand('TRUNCATE TABLE `test_transactions`;')->execute();
testNesting(4, 3, 1);
echo '<br>';
echo 'Rows:';
echo '<br>';
$rows = $connection->createCommand('SELECT * FROM `test_transactions`')->queryAll();
if($rows)
{
foreach($rows as $row)
{
print_r($row);
}
}
else
echo 'Table is empty!';
function testNesting(int $total, int $createErrorIn = null, int $current = 1)
{
if($current>=$total)
return;
$connection = Yii::app()->singleTransaction;
$indent = str_repeat(' ', ($current*4));
echo $indent.'Transaction '.$current;
echo '<br>';
$transaction=$connection->beginTransaction();
try
{
// create nonexisting columnname when we need to create an error in this nested transaction
$columnname = 'number'.($createErrorIn===$current ? 'rr' : '');
$connection->createCommand('INSERT INTO `test_transactions` (`'.$columnname.'`) VALUES ('.$current.')')->execute();
testNesting($total, $createErrorIn, ($current+1));
$transaction->commit();
}
catch(Exception $e)
{
echo $indent.'Exception';
echo '<br>';
echo $indent.$e->getMessage();
echo '<br>';
$transaction->rollback();
}
}
结果如下:
Transaction 1
Transaction 2
Transaction 3
Exception
CDbCommand failed to execute the SQL statement: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'numberrr' in 'field list'. The SQL statement executed was: INSERT INTO `test_transactions` (`numberrr`) VALUES (3)
Exception
Child transaction rolled back!
Exception
Child transaction rolled back!
Rows:
Table is empty!