???? Привет! Сегодня мы поговорим о том, как использовать вложенные транзакции в Bitrix, чтобы обеспечить целостность данных.
???? Тайны Транзакций
Версия main 22.200.0 принесла с собой нечто волшебное - вложенные транзакции в Bitrix! Зачем они нужны? Давайте посмотрим на этот процесс с точки зрения магии.
Транзакции - это ваше надежное магическое заклинание, которое гарантирует, что ваши множественные изменения в базе данных будут выполнены целиком или полностью отменены, если что-то пошло не так.
???? Для чего нужны транзакции?
Транзакции позволяют объединять несколько запросов в единую конструкцию. Она гарантирует, что запросы будут выполнены как единое целое.
Транзакции - это нечто большее, чем просто блок кода. Это своего рода защитный механизм, который помогает вам избежать проблем с целостностью данных. Они гарантируют, что ваши операции с базой данных будут либо успешными, либо полностью отменены, если что-то пошло не так.
???? Как это было раньше?
Прежде чем волшебство транзакций появилось в Bitrix, разработчики вынуждены были прибегать к тайным приемам.
Создание таблицы-шпиона, где каждое изменение регистрировалось. Это позволяло восстановить предыдущее состояние при ошибке.
Введение транзакций в Bitrix значительно улучшило этот процесс, предоставив более надежные и эффективные способы гарантировать целостность данных.
???? Волшебство в действии: Как это работает в коде?
<?php
$application = \Bitrix\Main\Application::getInstance();
$connection = $application->getConnection();
try {
$connection->startTransaction();
// Ваш волшебный код здесь...
$connection->commitTransaction();
} catch (\Exception $e) {
$connection->rollbackTransaction();
}
Окей, давайте рассмотрим пример использования вложенных транзакций на практике. Допустим, у нас есть сущность, назовем ее ExampleTable
. Мы хотим изменить поле NAME
у записи с кодом 'example'. При этом, мы также хотим добавить новую запись в другую таблицу - AnotherTable
.
<?php
$application = \Bitrix\Main\Application::getInstance();
$connection = $application->getConnection(); // Получаем соединение с базой данных
try {
// Начинаем транзакцию
$connection->startTransaction();
// Получаем из сущности некий объект
$object = \Example\ExampleTable::query()
->where('CODE', 'example')
->addSelect('NAME')
->fetchObject();
if ($object) {
$object->set('NAME', 'Пример записи'); // Меняем поле `NAME` у объекта
$result = $object->save(); // Сохраняем
if (!$result->isSuccess()) {
throw new \Bitrix\Main\SystemException('Ошибка в процессе сохранения данных.');
}
// Пробуем добавить второй элемент в другую таблицу
$anotherObject = \Example\AnotherTable::createObject();
$anotherObject->set('TITLE', 'New title');
$result = $anotherObject->save(); // Сохраняем
if (!$result->isSuccess()) {
// В случае ошибки откатываем транзакцию, и первый элемент не сохранится
throw new \Bitrix\Main\SystemException('Ошибка в процессе сохранения данных.');
}
// Успешно завершаем транзакцию
$connection->commitTransaction();
} else {
throw new \Bitrix\Main\SystemException('Объект не найден.');
}
} catch (\Exception $e) {
// Если произошла ошибка, отменяем транзакцию
$connection->rollbackTransaction();
}
В этом кусочке кода транзакция начинается, операции выполняются, и если что-то идет не так, мы можем легко и безопасно откатить все изменения.
???? Что делают методы управления транзакциями?
$connection->startTransaction();
: Этот метод начинает транзакцию, позволяя группировать несколько операций в единое целое.$connection->commitTransaction();
: Вызов этого метода фиксирует все изменения, сделанные в рамках транзакции, и отправляет их в базу данных.$connection->rollbackTransaction();
: Если произошла ошибка или необходимо отменить все изменения в рамках транзакции, этот метод откатывает все операции.
????️????️ Дополнительные тайны:
Вложенные транзакции применимы не только к
save()
, но и к другим методам ORM, таким какupdate()
,add()
. А также, к SQL запросам через$connection->query()
Если хотите углубиться в тайны магии транзакций, загляните в официальную документацию Bitrix
???? Завершение:
Таким образом, мы научились использовать волшебство транзакций в Bitrix, обеспечивая целостность данных. Если у вас есть свои волшебные методы или вопросы, делитесь в комментариях!
Комментарии (7)
SieMax
21.11.2023 21:02Если после startTransaction произойдет непредвиденное завершение работы приложения и до rollbackTransaction мы не доберёмся, что произойдет? Будет ли произведен автоматический откат при следующей инициализации ядра Bitrix?
isa3v Автор
21.11.2023 21:02+1Скорее при прекращении текущего соединения.
MySQL innodb автоматически откатывает все незакомиченные изменения.
FanatPHP
21.11.2023 21:02Это, кстати, хорошее замечание, которое как бы намекает, что в общем случае заключать транзакцию в try-catch в принципе и не нужно - транзакция и так откатится при закрытии соединения. Но учитывая, что явное лучше неявного, то традиционно мы это делаем. Тем более что некоторых обстоятельствах (персистентное соединение с БД, например) надо все-таки, хотя бы попытаться.
FanatPHP
21.11.2023 21:02+1Кстати, собрался прочитать текст по ссылке. Тема "вложенности" транзакций в статье не раскрыта от слова совсем.
Я ещё вчера очень удивился, поскольку при чтении складывается полное впечатление, будто в битрикс добавили поддержку транзакций вообще, а раньше их не было. А слово "вложенные" просто вкралось по ошибке. И только прочитав документацию, понял, о чем речь.
Сама по себе поддержка транзакций в битриксе была давно (что и неудивительно, поскольку там явно растут ноги из PDO). А появилась поддержка именно "вложенных" транзакций, собранная из SAVEPOINT и палок, что позволило использовать транзакции не только в "вызывающем (конечном) сценарии" (что бы это не значило), "но и в API". В то время как ранее...могла сложиться ситуация, когда конечный сценарий открывает транзакцию, вызываемый API тоже открывает свою транзакцию, в итоге все коммиты и роллбеки перемешиваются непредсказуемым образом.
То есть новость должна звучать не "В битриксе появилась поддержка транзакций", а "теперь транзакции можно использовать как в конечном приложении, так и в API".
rpsv
21.11.2023 21:02Статья про транзакции, а не про вложенность. Вложенность работает так под капотом:
$db = \Bitrix\Main\Application::getConnection(); try { // START TRANSACTION $db->startTransaction(); // SAVEPOINT 1 $db->startTransaction(); // SAVEPOINT 2 $db->startTransaction(); // - $db->commitTransaction(); // ROLLBACK TO SAVEPOINT 1 (внутри кидает TransactionException) $db->rollbackTransaction(); // DON'T EXECUTE $db->commitTransaction(); } catch (\Bitrix\Main\DB\TransactionException $e) { // ROLLBACK $db->rollbackTransaction(); } catch (Throwable $e) { // ROLLBACK or ROLLBACK TO SAVEPOINT * $db->rollbackTransaction(); throw $e; }
FanatPHP
Два классических замечания к новичковым попыткам начать использовать транзакции:
Во-первых, Exception ловит только половину возможных причин, по которым выполнение кода может быть прервано.
Во-вторых, обеспечить целостность данных в случае ошибки - это, конечно, хорошо. Но вот замалчивать ошибку при этом не стоит. Откатив транзакцию, надо проинформировать об ошибке программиста.
Поэтому
Понятно, что от бессмысленного лепета "'Ошибка в процессе сохранения данных." толку мало, но это, как я понимаю, издержки битрикса. Возможно, когда-нибудь он научится сам кидать нормальные исключения. А пока хоть это пусть выдаёт.
isa3v Автор
В статье акцент сделан на транзакциях, чтобы упростить и подчеркнуть основную тему. Поэтому обработка исключений представлена более простым образом.
В реальных проектах, конечно же, рекомендуется более детально обрабатывать ошибки. Ваши замечания в этом смысле абсолютно верные