Сценарий
- Имеется длинная транзакция, которая должна либо завершиться, либо полностью отмениться
- Внутри транзакции создается большое количество объектов в БД
- Необходимо собрать все возможные ошибки обрабатываемых объектов, а не падать на первой попавшейся ошибке
Описание приложенной конфигурации
- Документ1 - документ, инициирующий длинную транзакцию. Служит для сохранения ошибок
- Документ2 - документ, способный вызвать исключение:
- вне транзакции - события модуля объекта ОбработкаПроверкиЗаполнения. Генерируем случайное число. Если не делится на 2, то вызываем исключение
- в транзакции - событие модуля объекта ПриЗаписи. Генерируем случайное число. Если не делится на 3, то вызываем исключение
- Команда Обработать в форме списка документа Документ1
- Создает документ Документ1
- Начинает длинную транзакцию
- Создает 100 документов Документ2 в цикле
- Для каждого документа проверяется заполнение и запись - соответственно будет исключение в транзакции или без
- Сохраняет ошибки создания документов Документ2
- Записывает документ Документ1
Классическая схема
- Правила использования транзакций описаны на сайте ИТС: Транзакции: правила использования
- Пример кода из приложенной конфигурации:
- Создается документ Документ1 - служит для хранения ошибок обработки
- Начинается длинная транзакция
- Внутри транзакции создаются документы Документ2 (код будет ниже), которые могут вызывать исключения (в транзакции или без)
Процедура СоздатьДокумент1()
Док = Документы.Документ1.СоздатьДокумент();
Док.Дата = ТекущаяДатаСеанса();
НачатьТранзакцию();
Попытка
СоздатьДокументы2(Док);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Сообщить(ОбработкаОшибок.КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
Док.Записать();
КонецПроцедуры
МИНУСЫ:
- Первое исключение внутри транзакции останавливает дальнейшую обработку объектов
- Нет возможности увидеть ошибки после исключения (картинка ниже)
Вариант 1. Добавляем попытку
- Каждый обрабатываемый объект внутри длинной транзакции оборачиваем в попытку
- В случае исключения длинная транзакция продолжается
- Если в попытке есть своя транзакция (явная или неявная) и в ней произошло исключение (кружок №2 на картинке), то все последующие обращения к БД будут вызывать ошибку В данной транзакции уже происходили ошибки
Процедура СоздатьДокументы2(Док)
БылиОшибки = Ложь;
Для Индекс = 1 По 100 Цикл
НоваяСтрока = Док.Данные.Добавить(); // Новая строка в документе 1 (для ошибки)
Попытка
СоздатьДокумент2(); // Тут возможны исключения в транзакции и без
НоваяСтрока.Ошибка = НСтр("ru = 'Ошибок нет'");
Исключение
БылиОшибки = Истина;
НоваяСтрока.Ошибка = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
КонецЦикла;
Если БылиОшибки Тогда
ВызватьИсключение НСтр("ru = 'В процессе обработки были ошибки, изменения не сохраняем'");
КонецЕсли;
КонецПроцедуры
МИНУСЫ:
- После первого исключения внутри "маленькой" транзакции все последующие ошибки будут неинформативными (картинка ниже)
- И даже если в обрабатываемой строке не должно быть ошибки (но есть запись в БД), то все равно будет ошибка В данной транзакции уже происходили ошибки
Вариант 2. Находим сломанные транзакции
- Выявляем момент, когда в транзакции уже происходили ошибки
- Это можно сделать выполнив простейший запрос ВЫБРАТЬ 1 (решение предложено тут Безопасная работа с транзакциями на встроенном языке)
- Если такой момент выявили, то отменяем транзакцию и тут же начинаем новую
- Получается мы "режем" длинную транзакцию на части, давая возможность продолжить обращаться к БД и выявлять все последующие ошибки
- Если во время длинной транзакции не произошло ни одной ошибки, то она успешно завершится
- Если произошла хотя бы одна ошибка в конце обработчика отменяем транзакцию
Процедура СоздатьДокументы2(Док)
БылиОшибки = Ложь;
Для Индекс = 1 По 100 Цикл
НоваяСтрока = Док.Данные.Добавить(); // Новая строка в документе 1 (для ошибки)
Попытка
Если ВТранзакцииПроисходилиОшибки() Тогда
ОтменитьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
СоздатьДокумент2(); // Тут возможны исключения в транзакции и без
НоваяСтрока.Ошибка = НСтр("ru = 'Ошибок нет'");
Исключение
БылиОшибки = Истина;
НоваяСтрока.Ошибка = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
КонецЦикла;
Если БылиОшибки Тогда
ВызватьИсключение НСтр("ru = 'В процессе обработки были ошибки, изменения не сохраняем'");
КонецЕсли;
КонецПроцедуры
Ожидаемый результат:
PS. Вариант без транзакций
- Создаем объекты (каждый в своей попытке) и сохраняем на них ссылки (в массив)
- Если произошла ошибка, то сохраняем её (в массив ошибок)
- Если в процессе обработки произошла хоть одна ошибка, то помечаем объекты массива на удаление и затем физически удаляем, а пользователю выводим ошибки из массива ошибок
МИНУСЫ:
- Появляется куча объектов, которые нужно корректно удалить
- Если в процессе удаления произойдет сбой, то объекты останутся в системе и тут только ручное вмешательство
- Если на созданные объекты будут ссылки в других объектах, то так же автоматически они не удалятся, опять ручное вмешательство
Именно от такого подхода избавлялся