Help us understand the problem. What is going on with this article?

トランザクション処理を解説「初心者向け」

More than 1 year has passed since last update.

これはユアマイスター Advent Calendar 2018の17日目の記事です。

サンプルコードを元にトランザクション処理を解説します。

サンプルコード

sample
<?php

namespace App\Controller;

use App\Controller\AppController;
use Cake\Datasource\ConnectionManager;

/**
 * Test Controller
 */
class TestController
{
    public function sample()
    {
        if (!$this->request->is('post') && empty($this->request->data)) {
            $this->Flash->error("不正なリクエストです");
            return $this->redirect($this->referer());
        }

        $connection = ConnectionManager::get('default');

        $connection->begin();
        try {
            $hogeUpdate = $this->Hoge->query()
            ->update()
            ->set([
                'status_id' => 3,
            ])
            ->where(['hoge.id' => $this->request->data()]);

            if (!$hogeUpdate->connection->execute()) {
                throw new Exception();
            }

            $hugaUpdate = $this->Huga->query()
            ->update()
            ->set([
                'flg' => 1,
            ])
            ->where(['Huga.user_id' => $this->Auth->user()]);

            if (!$hugaUpdate->connection->execute()) {
                throw new Exception();
            }

            $connection()->commit();
        } catch (Exception $e) {
            $this->Flash->error('exception');
            $connection->rollback();

            return $this->render('hoge');
        }
    }
}

TestControllerクラスにsampleというアクションを作成しました。

コード上$this->request->data()$this->Auth->user()があるので、前提としては、ログインユーザーがある処理をした際にこのsampleアクションにPOSTされる、という形です。

処理の大まかな流れ

  1. POSTデータか判定
  2. トランザクションを張る
  3. HogeHugaテーブルのある値を更新。
  4. これらの処理をコミットする
  5. 更新処理に失敗したら例外を投げ、View側にFlashメッセージを表示、トランザクション内の処理をrollbackし、ある画面に遷移する

解説

ここからはトランザクションをメインに解説します。
DBの値を更新する処理の解説は致しません。

トランザクションとは

トランザクション処理は、既知の一貫した状態のデータベースを維持するよう設計されており、相互依存のある複数の操作が全て完了するか、全てキャンセルされることを保証する。

トランザクション処理では、データベースの個々の操作が自動的に1つに連結され、不可分のトランザクションとされることがある。トランザクション処理システムは、1つのトランザクション内の全操作がエラー無しに成功するか、全操作が実行されないことを保証する。一部の操作が成功し、他の操作でエラーが発生した場合、トランザクション処理システムはそのトランザクションの「全」操作を「ロールバック; roll back」し、そのトランザクションによる痕跡を消去してデータベースを一貫した状態(そのトランザクションを開始する前の状態)にリストアする。あるトランザクションの全操作が完了した場合、そのトランザクションはシステムによって「コミット; commit」され、データベースに加えられた更新内容が恒久的なものとなる。コミットされたトランザクションがロールバックされることはない。

引用元:Wikipedia

長々と引用申し訳ありません。

つまりトランザクションとは一つの処理のまとまりを表しています。

先ほど処理の流れで

  1. POSTデータか判定
  2. トランザクションを張る
  3. HogeHugaテーブルのある値を更新。
  4. これらの処理をコミットする
  5. 更新処理に失敗したら例外を投げ、View側にFlashメッセージを表示、トランザクション内の処理をrollbackし、ある画面に遷移する

と書きましたが、この2~4までが1トランザクションとなります。
この処理が引用元でも記載されているように、相互依存のある複数の操作が全て完了するか、全てキャンセルされることを保証するという部分に当たります。

サンプルコードでは1トランザクション内で複数のテーブルの値を更新しています。

Hogeテーブルの値の更新に成功したとしても、Hugaテーブルのテーブルの更新に失敗した場合、この1トランザクション内の処理自体がキャンセルされ、ロールバックされます。
つまりHogeテーブルの値は、更新前の状態になります。

なんでトランザクションを張るのか?

端的にいうとデータの整合性を担保する為です。

サンプルコードを元に話しますと、Hogeテーブルのstatus_id3に更新し、Hugaテーブルのflg1に更新しています。

これらの更新処理は1トランザクションであり、Hogeテーブルのstatus_id3であるならば、Hugaテーブルのflg1である事がデータの整合性が取れている、と言えるとします。

もし、Hogeテーブルのstatus_id3で、Hugaテーブルのflg0の場合、データの整合性は取れていない事になります。

トランザクションを貼る事で、もし一方の処理に不具合が発生した場合、トランザクション内の処理をロールバック、つまり処理を行う前の状態に戻してくれます。(コード的にはロールバックしてくれる、というよりトランザクション内の処理に不具合が発生した場合、ロールバックするように実装する、という方が正しいですが。)

このトランザクションの仕組みがデータの整合性を担保してくれると言えます。

参考:データベースのトランザクション処理とは:不可分なひとつながりの処理|データ分析用語を解説
こちらのデータベースのトランザクション処理を図示した場合という部分が可視化されていてわかりやすいので、掲載致します。

CakePHPのConnectionクラス

Connectionクラスには上記で話してきたトランザクションの機能を提供しています。
なので、Connectionクラスを使用してトランザクションを張ります。

サンプルコードを例に記載します。

まずuse Cake\Datasource\ConnectionManager;をしてエイリアスを作成します。

ConnectionManager::get('default')で接続します。
defaultを引数に指定する事で、設定ファイルに記載した接続情報を参照します。

$connection->begin();はSQLのBEGIN構文と同じで、開始を宣言します。

if (!$hogeUpdate->connection->execute()) {
    throw new Exception();
}

こちらの処理で更新処理に失敗したら例外を投げる用にしてます
execute()は発行したクエリの実行をするメソッドです

$connection()->commit();はSQLのCOMMIT構文と同様で、トランザクション内の処理をコミットして永続的なものとします。

$connection->rollback();は、SQLのROLLBACK構文と同様で、トランザクション内の処理をロールバックして取り消します。

今回のサンプルコードではORMを使用してクエリを発行していますが、execute()メソッド内でMySQLのクエリを書くことも可能です。

$hogeUpdate = $this->Hoge->query()
->update()
->set([
    'status_id' => 3,
])
->where(['hoge.id' => $this->request->query()]);

例えばこちらの処理の場合、

$connection->execute('UPDATE hoge SET status_id = ? WHERE id = ?', [
    3,
    $this->request->query['status_id']
]);

このような形に書き換えられます。
?の順番とexecute()の第二引数の連想配列の順番は対応しているので、status_idには3が入る、みたいな形になります。

参考

https://book.cakephp.org/3.0/ja/orm/database-basics.html#connection
https://book.cakephp.org/3.0/ja/orm/database-basics.html#id11
https://book.cakephp.org/3.0/ja/orm/database-basics.html#database-configuration
https://dev.mysql.com/doc/refman/5.6/ja/commit.html

おまけ

Twitterやってます!外部のエンジニアの方ともどんどん繋がりたいと考えていますので、是非フォローして頂ければと思います!@Tatsuo96
p.s.今年の2月までは溶接職人でした。ガッツ系エンジニアとしてやらせて頂いております。

tatsuo-iriyama
Web Engineer|96'|高卒|前職:溶接工|2019-01〜ユアマイスター株式会社|
yourmystar
サービス産業のIT化プラットフォーム「ユアマイスター」と大切なものをもっと大切にするメディア「ユアマイスター スタイル」を運営するスタートアップです。
http://corp.yourmystar.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした