CakePHP3で開発をしていて、PHPUnitで少し複雑なテストをしようとごにょごにょやってたら、トランザクションをネストしてロールバックしたくなったので調べました。
SQLにおけるトランザクションはネストできるか?
本記事でいうトランザクションとはSQLのアレです。コレです。ではそもそもトランザクションとはネストできるものなのかというと、通常はできません。詳しくは下記の記事に詳しく書いてありました。
トランザクションをネストしたらどうなる? 内側だけロールバックできる? - Qiita
記事によると、TRANSACTION命令だけでなく、SAVEPOINTを使えば部分的ロールバックが可能だということです。CakePHP3ではどうすればよいのでしょうか。
結論:CakePHP3ではトランザクションをネストできる
結論から先に言うと、CakePHP3では普通に**トランザクションをネストできます。**内部的には前出のSAVEPOINTを利用して実現されています。詳細はcakephp/src/Database/Connection.phpのbegin()メソッドのソースを見ればすぐわかります。CakePHP 3.4時点でのコードを引用してみます。
/**
* Starts a new transaction.
*
* @return void
*/
public function begin()
{
if (!$this->_transactionStarted) {
if ($this->_logQueries) {
$this->log('BEGIN');
}
$this->_driver->beginTransaction();
$this->_transactionLevel = 0;
$this->_transactionStarted = true;
$this->nestedTransactionRollbackException = null;
return;
}
$this->_transactionLevel++;
if ($this->isSavePointsEnabled()) {
$this->createSavePoint($this->_transactionLevel);
}
}
こんな感じで、begin()メソッドをネストして使うと最初のif文の!$this->_transactionStarted
がfalseになるためSAVEPOINTの作成処理が実行されます。部分的ロールバック等も普通にできます。
注意点としては、$this->isSavePointsEnabled()
でfalseが返ってくる場合はネストできません(少し前のバージョンだと$this->useSavePoints()
だったりします)。こちらはドライバの実装に依ると書かれていますが、今のところMySQL, Postgres, SQLite, SQL Serverの全てで利用可能であるようです。
というわけで、基本的には何も考えずbegin()を複数回ネストしても大丈夫です。Cake側でよしなにやってくれます。Cake万歳。