【Laravel】複数のDB処理を行う際はtransactionを使う

Laravelで複数のDB処理を行う場合は
transactionを使ってデータの整合性を保つのが良い

transaction (トランザクション)とは

複数のデータベース処理を、1つの処理としてまとめるものトランザクションという
トランザクションを活用することで、データの整合性を保つことが可能になる

  • 全てのDB処理が成功した場合 ... 一連の処理を確定 (=コミット) する
  • いずれかのDB処理が失敗した場合 ... 一連の処理を取り消し (=ロールバック) する


例えば...

CSVインポートにて、複数の会員情報を一括登録する場合

20人目の会員情報に不備があって登録失敗した際、すでに実行している19人分の登録処理を取り消しすることができる
→ エラー修正後、再度同じCSVを使用して重複することなく登録が可能に

ユーザー情報登録にて、基本情報と詳細情報を分けて登録する場合

詳細情報に不備があって登録失敗した場合、基本情報の登録を取り消しすることができる
→ 基本情報はあるが詳細情報はないという不整合を防ぐことができる

このように、処理が全て成功しない限りDBに変更は確定されないという優れもの

実際に処理を書いてみる

LaravelではDBファザードを使用してトランザクションを簡単に使用することができる

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; // ★ DBファザードをuse

class UserController extends Controller
{
    /**
     * 登録処理
     *
     * @param Request $request
     * @return \Illuminate\Routing\Redirector
     * @throws \Exception
     */
    public function store(Request $request)
    {
        try {
            // ★ トランサクションを開始する
            DB::beginTransaction();
            
            // 基本情報を登録する
            $user = User::create([
              'name' => $request->name,
              'email' => $request->email
            ]);
            // 詳細情報を登録する
            UserDetail::create([
              'user_id' => $user->id,
              'job' => $request->job,
              'age' => $request->age
            ]);           
       
            // ★ 変更を確定 (コミット)する
            DB::commit();
        
        } catch (\Exception $e) {
            // ★ 変更を取り消し (ロールバック)する
            DB::rollback();
            
            throw new \Exception($e);
        }

        // (以下省略) 登録後のリダイレクトなど
    }
}
ざっくり解説
  • 使用しているのは以下のメソッド
    DB::beginTransaction(); ... トランザクションを開始する
    DB::commit(); ... 変更を確定する
    DB::rollback(); ... 変更を取り消しする
  • 処理の流れ
    • try-catchを使用して一連の処理を囲う
    • どこかで処理が失敗した場合はcatch (\Exception $e) {}に入ってくるので、そこでロールバックをして変更を全て取り消しする
    • 最後まで処理が失敗せずにたどり着いた場合は、コミットをして変更を確定する

コミット漏れには注意

たまにあるのが、登録・更新が成功しているはずなのにDBに反映されないというもの
その場合はコミットの書き忘れが原因のことが多い

トランザクションを使用する場合
DB::commit();を書き忘れると変更は確定されない (=DBに反映されない)
同じく、DB::rollback();を書き忘れると変更の取り消しはされない

不安な場合は、以下のようにクロージャを使用して自動コミットすることも可能

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; // ★ DBファザードをuse

class UserController extends Controller
{
    /**
     * 登録処理
     *
     * @param Request $request
     * @return \Illuminate\Routing\Redirector
     * @throws \Exception
     */
    public function store(Request $request)
    {
        try {
            DB::transaction(function () use($request) {
                // 基本情報を登録する
                $user = User::create([
                    'name' => $request->name,
                    'email' => $request->email
                ]);
                // 詳細情報を登録する
                UserDetail::create([
                    'user_id' => $user->id,
                    'job' => $request->job,
                    'age' => $request->age
                ]);  
            });  
        } catch (\Exception $e) {
           throw new \Exception($e);
        }

        // (以下省略) 登録後のリダイレクトなど
    }
}

DB::transaction(function () use($request) {}
で囲ってあげると、その中の処理が完了時に自動でコミットをしてくれる
どこかで失敗した場合は自動でロールバックもしてくれる

useには中で使用したい変数を記載。以下のようにカンマで区切ると複数渡すこともできる
DB::transaction(function () use($request, $data) {}

複数のDB処理だけでなく、マルチな処理にも役にたつ

複数のDB処理を行う場合に限らず、
「DB処理 & メール通知」や「DB処理 & API連携」など、
マルチな処理でもトランザクションは役に立つ

マルチな処理の例
  • 会員登録機能の場合
    1. 会員情報をDBに登録する
    2. 登録通知をメール送信する
  • メルマガ配信機能の場合
    1. 配信情報をDBに登録する
    2. メール送信のjobを登録する
  • 商品購入機能の場合
    1. 購入情報をDBに登録する
    2. 外部決済APIを使用して決済を行う
    3. 取得した決済関連の識別情報等をDBに登録する
    4. 決済完了通知をメール送信する
  • ソース例
    以下では、DB登録後のメール通知でエラーが出た場合、
    ロールバックによってDB登録は取り消しされる
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use App\Notifications\UserRegisteredNotification;
use Illuminate\Support\Facades\DB; // ★ DBファザードをuse

class UserController extends Controller
{
    /**
     * 登録処理
     *
     * @param Request $request
     * @return \Illuminate\Routing\Redirector
     * @throws \Exception
     */
    public function store(Request $request)
    {
        try {
            // ★ トランサクションを開始する
            DB::beginTransaction();
            
            // DBを登録する
            $user = User::create([
              'name' => $request->name,
              'email' => $request->email,
            ]);
            
            // メール通知を行う
            $user->notify(new UserRegisteredNotification($user));       
       
            // ★ 変更を確定 (コミット)する
            DB::commit();
        
        } catch (\Exception $e) {
            // ★ 変更を取り消し (ロールバック)する
            DB::rollback();
            
            throw new \Exception($e);
        }

        // (以下省略) 登録後のリダイレクトなど
    }
}

マルチな処理でもトランザクションを使用することにより、
DBを登録した後にその後の処理が失敗する = システムの整合性が取れなくなってしまう
といった問題を未然に防ぐことができる

\ 案件のご依頼・ご相談はこちらから /