Используем транзакции в Laravel
Доброго времени суток. В данной статье мы поговорим о том, как использовать транзакции в Laravel. Транзакция — это атомарное действие над базой данных. Сложно? Ничуть. Дальше мы на примере рассмотрим, что такое транзакция в БД. И как их применять в Laravel.
Транзакции в базе данных.
При работе с базой данных мы должны поддерживать целостное состояние. И при изменении данных в базе данных мы должны перейти от одного целостного состояния к другому. Но возникают ситуации, когда при обновлении данных состояние целостности нарушается. Например, у нас есть два счета и мы хотим перевести средства с одного счета на другой. С точки зрения SQL:
UPDATE accounts SET amount=amount-100 WHERE account="account_1" UPDATE accounts SET amount=amount+100 WHERE account="account_2"
Все просто. Но, если выполнится один запрос, а второй нет, то целостное состояние будет нарушено. Во избежание таких ситуаций в СУБД ввели понятие транзакции – атомарного воздействия на данные. Т.е. перевод базы данных из одного целостного состояния в другое. Другими словами, в транзакцию мы включаем несколько запросов, которые должны быть выполнены все, если же хоть один не будет выполнен, то и все запросы входящие в транзакцию будут не выполнены.
Подготовка проекта на Laravel.
У меня установлен Laravel 5.5.4
Настроено подключение к базе данных. В базе данных есть таблица accounts с данными.
Есть модель Account.
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Account extends Model { protected $table = 'accounts'; protected $fillable = ['account', 'amount']; }
Есть контроллер TestController с одним методом:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Account; class TestController extends Controller { public function index() { } }
И есть роут:
Route::get('test', 'TestController@index');
Давайте реализуем перевод суммы с одного аккаунта на другой. Для этого изменим метод index в контроллере TestController:
public function index() { Account::where('account','account_1') ->decrement('amount',100); Account::where('account','account_2') ->increment('amount',100); }
Проверяем:
Мы с вами сделали перевод с одного аккаунта на другой не используя транзакций. И в нашем случае не произошло каких-либо сбоев. Поэтому и целостность состояния не нарушена.
Перевод средств с нарушением целостности состояния
Давайте вернем данные к исходным. На сумма на аккаунте 1 равна 1000, а на аккаунте 2 – 0.
Теперь сделаем перевод средств с одного аккаунта на другой с ошибкой. Для этого специально допустим ошибку в методе index контроллера TestController.
public function index() { Account::where('account','account_1') ->decrement('amount',100); Account::where('account_','account_2') ->increment('amount',100); }
Проверяем:
Первый запрос прошел, а во втором возникла ошибка. И в итоге: средства с первого аккаунта ушли, а на второй не пришли. Целостность состояния нарушена. Чтобы такого не происходило и используются транзакции.
Перевод средств с использованием транзакции.
Давайте вернем данные к исходным: сумма на первом аккаунте равна 1000, а на втором 0.
Теперь поместим два наших запроса в транзакцию. Для этого в Laravel используется метод transaction фасада DB.
Изменим наш метод index контроллера TestController:
public function index() { DB::transaction(function(){ Account::where('account','account_1') ->decrement('amount',100); Account::where('account_','account_2') ->increment('amount',100); }, 5); }
Метод transaction принимает два параметра callback – обязательный и второй параметр не обязательный – это количество попыток (после использования, которых транзакция считается не успешной).
В методе index мы опять умышлено допустили ошибку, во втором запросе. Но сейчас оба запроса находятся в транзакции. Поэтому ни один не должен выполниться.
Проверяем:
При выполнении второго запроса возникла ошибка. Из-за этого в целом транзакция не выполнена. Суммы на аккаунтах не изменились.
Давайте поправим метод index.
public function index() { DB::transaction(function(){ Account::where('account','account_1') ->decrement('amount',100); Account::where('account','account_2') ->increment('amount',100); }, 5); }
Проверяем:
Все запросы выполнены без ошибок, поэтому транзакция прошла успешно. Суммы на аккаунтах изменились.
В Laravel в фасаде DB есть еще несколько методов для управления транзакцией:
- DB::beginTransaction() – для определения транзакции
- DB::rollBack() – для отмены всех запросов после DB::beginTransaction()
- DB::commit() – для выполнения всех запросов после DB::beginTransaction()
Заключение.
Мы с вами разобрались для чего нужны транзакции в СУБД. И реализовали транзакции в Laravel. Рассмотрели два способа управления транзакциями: автоматический с помощью DB::transaction и ручное с помощью: DB::beginTransaction(),DB::rollBack(),DB::commit()