CakePHP5でのトランザクションの実験 まとめ

CakePHP
この記事は約5分で読めます。

(本記事は書きかけで随時更新しております。)

MariaDB + CakePHP5 で実験したことをまとめる

基本情報

MariaDB

  • MariaDBのデフォルトの分離レベル は、 REPEATABLE-READ である。
  • ネストしたトランザクションを使うにはSAVEPOINTを利用する。
  • TRUNCATE はロールバックできない。

PDO

  • BEGIN でトランザクションが始まった後、COMMIT , ROLLBACK が明示的に発行しないと、自動的にROLLBACKとなる。

CakePHP5

  • コネクションオブジェクトはシングルインスタンスである。
  • CakePHPのコネクションオブジェクトはPDOを拡張しものである。
  • テーブルオブジェクトのsave()メソッドは、デフォルトでトランザクション単位で行われる。
  • newEntity( data ) を使う時は、自動的にvalidationが働くが、entityオブジェクトのフィールドプロパティに、直接データを代入するときは、働かない。必要なら手動でvalidate() する必要がある。
  • save() 時には entity にエラーがある(validation エラーが含まれている)場合、あるいは、buildRulesのチェックに失敗したときには、戻り値が false となる。データベースでのエラーはSQLException がスローされる。

実験でわかったこと

CakePHP5

デフォルトでSAVEPOINTは使われない。

このことは、デフォルトでネストしたトランザクションは使えないことを意味する。ネストできないため、トランザクション内のsave()メソッドはトランザクションを利用しない。

SAVEPOINTを利用する、またネストしたトランザクションを有効にするためには、明示的にSAVEPOINTを利用することを宣言しなければならない。

$connection = ConnectionManager::get('default');
$connection->enableSavePoints(true); // savepoint 有効
SAVEPOINTを利用する場合、rollback() に気を付ける

SAVEPOINTが有効なとき、rollback(true) としたときに、全てのトランザクションがロールバックとなる。

transactional()メソッドを使う場合、ネストしたトランザクションが絡む場合に気を付ける

問答無用でbegin() を実行し、勝手にSAVEPOINTが有効にはならないので、外側のトランザクションがあると強制コミットとなる。

このメソッド内での例外は、呼び出し側に再スローされる。

CakePHP のコネクションオブジェクトにはトランザクション関連のメソッドが多くある

詳細は、APIドキュメント参照。
https://api.cakephp.org/5.1/class-Cake.Database.Connection.html

enableSavePoints()SAVEPOINTを使えるようなら使う。
isSavePointsEnabled()SAVEPOINTが有効かどうか
disableSavePoints()SAVEPOINTを無効にする。
createSavePoint( string|int $name )セーブポイントを作成する
releaseSavePoint( string|int $name )作成したセーブポイントを削除する。
rollbackSavepoint( string|int $name )名前でセーブポイントをロールバックする。
rollback()トランザクションを先頭までロールバックするかどうか。セーブポイントを使用する場合はデフォルトで false になり、使用しない場合は true になる。
inTransaction()トランザクション中かどうか
__debugInfo()多分デバッグ用に用意されていて、protectedな変数を見られる。

トランザクションレベルのプロパティもあるが、protected で公開されていない。しかし上記のように、__debugInfo() を使えばよいことが分かった。

コーディングでやった方が良いこと

デフォルトでSAVEPOINTを有効にする

MariaDB固定であれば、最初からSAVEPOINTを有効にしておく方が良い。

  class AppController extends Controller
    public function initialize(): void
    {
        parent::initialize();

        /** @var Cake\Database\Connection */
        $connection = ConnectionManager::get('default');
        $connection->enableSavePoints(true); // savepoint 有効
......

トランザクションはメソッド内で必ず完結させる

begin() で始まったトランザクションは必ず同トランザクションレベルのrollback(),commit()を通るように設計する。try..catch 構文を必ず利用する。

ORMとの併用時に注意する

ORMの各メソッドの使用時には、バリデーションエラー(validation)、ビルドルールエラー(buildRules)、データベースエラーに対応する必要がある。データベースエラーには、try..catch でしか対処できない。

また、トランザクションが有効(無効にできるオプションもある)なのでトランザクション内で使う時は、SAVEPOINTが無効の場合予期せぬ動きとなる。

タイトルとURLをコピーしました