рубрики: Язык программирования 1С | Дата: 25 апреля, 2017
В прошлый раз мы рассмотрели простейший способ явной организации транзакций средствами встроенного языка 1С. На практике транзакции гораздо чаще используются совместно с конструкцией Попытка — Исключение. Это позволяет в случае ошибки продолжить выполнение кода, а также выдать адекватное сообщение об ошибке пользователю и записать информацию в журнал регистрации или в файл логов для последующего анализа администратором системы.
Если мы обратимся к технической документации или к диску ИТС, то увидим, что фирма 1С рекомендует следующий способ организации транзакции в попытке
Попытка
//1. Начало транзакции.
НачатьТранзакцию();
//2. Блок операций, выполняющихся в транзакции.
//3. Если все операции успешны, фиксируем транзакцию.
ЗафиксироватьТранзакцию();
Исключение
//4. Если при выполнении кода возникли ошибки, отменяем транзакцию.
ОтменитьТранзакцию();
//5. При необходимости запись в журнал регистрации.
//6. При необходимости вывод сообщения пользователю.
КонецПопытки;
Собственно каких-то особых пояснений код не требует. Если в процессе попытки выполнения транзакционного кода возникает ошибка, мы сразу проваливаемся в блок исключение, т.е. до метода ЗафиксироватьТранзакцию() мы просто не доходим. Ну а в исключении соответственно отменяем транзакцию и если это необходимо выводим сообщение об ошибке и записываем информацию в журнал регистрации. Фиксировать ошибки в журнале регистрации крайне желательно, особенно для тех операций, которые выполняются без участия пользователя (например, регламентные задания). Это позволит в дальнейшем проанализировать ошибку. Вместо записи в журнал регистрации можно организовать отправку сообщений администратору по электронной почте.
Теперь вооружившись новыми знаниями попробуем видоизменить код, рассмотренный в статье про простейшие транзакции. Напомню, что мы рассматривали запись в справочник Товары и в регистр сведений Цена по следующей схеме:
&НаСервереБезКонтекста
Процедура ВыполнитьТранзакциюНаСервере()
НачатьТранзакцию();
//записываем новый товар
Товар = Справочники.Товары.СоздатьЭлемент();
Товар.Наименование = "Дырокол";
Товар.Записать();
//записываем цену
НаборЗаписей = РегистрыСведений.Цена.СоздатьНаборЗаписей();
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = ТекущаяДата();
НоваяЗапись.Товар = Товар.Ссылка;
НоваяЗапись.Сумма = 100;
НаборЗаписей.Записать();
ЗафиксироватьТранзакцию();
КонецПроцедуры
А теперь поместим транзакцию в блок Попытка Исключение. Скорее всего ошибки могут возникнуть только в момент записи в справочник или в регистр сведений, поэтому предварительную подготовку вынесем за пределы транзакции.
&НаСервереБезКонтекста
Процедура ВыполнитьТранзакциюНаСервере()
//создаем новый товар
Товар = Справочники.Товары.СоздатьЭлемент();
Товар.Наименование = "Дырокол";
//Создаем запись с ценой
НаборЗаписей = РегистрыСведений.Цена.СоздатьНаборЗаписей();
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = ТекущаяДата();
НоваяЗапись.Сумма = 100;
//Выполняем транзакцию в попытке
Попытка
НачатьТранзакцию();
Товар.Записать();
НоваяЗапись.Товар = Товар.Ссылка;
НаборЗаписей.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Произошла ошибка при записи товара и его цены";
Сообщение.Сообщить();
ЗаписьЖурналаРегистрации("Произошла ошибка при записи товара и его цены");
КонецПопытки;
КонецПроцедуры
У тех кто только начинает работать с транзакциями зачастую возникает желание сделать вот таким образом
НачатьТранзакцию();
Попытка
НачатьТранзакцию();
//Блок операций
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
КонецПопытки;
Попытка
НачатьТранзакцию();
//Блок операций
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
КонецПопытки;
ЗафиксироватьТранзакцию();
Или в цикле
НачатьТранзакцию();
Для каждого Данные Из МассивДанных Цикл
Попытка
НачатьТранзакцию();
Данные.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
КонецПопытки;
КонецЦикла;
ЗафиксироватьТранзакцию();
На первый взгляд мы сделали все в соответствии с рекомендациями фирмы 1С. Но дело в том, что платформа 1С не поддерживает вложенные транзакции. То есть чисто технически так писать можно. Но при этом все вложенные транзакции не образуют новые, а относятся к той же самой транзакции верхнего уровня. Таким образом, если в одной из вложенных транзакций произойдет ошибка, следующую вложенную транзакцию нельзя будет зафиксировать. Система выдаст сообщение вида: «В данной транзакции уже происходили ошибки!». Продемонстрируем это на примере. Допустим мы решили записать два товара, каждый в своей транзакции. И сделаем эти транзакции вложенными в третью. Далее искусственно вызовем ошибку в первой транзакции с помощью метода ВызватьИсключение:
&НаСервереБезКонтекста
Процедура ВыполнитьТранзакциюНаСервере()
НачатьТранзакцию();
Попытка
НачатьТранзакцию();
Товар = Справочники.Товары.СоздатьЭлемент();
Товар.Наименование = "Стол";
Товар.Записать();
ВызватьИсключение "Ошибка записи товара.";
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = ОписаниеОшибки();
Сообщение.Сообщить();
КонецПопытки;
Попытка
НачатьТранзакцию();
Товар = Справочники.Товары.СоздатьЭлемент();
Товар.Наименование = "Стул";
Товар.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = ОписаниеОшибки();
Сообщение.Сообщить();
КонецПопытки;
ЗафиксироватьТранзакцию();
КонецПроцедуры
В результате выполнения этой процедуры увидим в окне сообщений следующее:
{ВнешняяОбработка.ТранзакцииВПопытке.Форма.Форма.Форма(20)}: Ошибка записи товара.
{ВнешняяОбработка.ТранзакцииВПопытке.Форма.Форма.Форма(40)}: Ошибка при вызове метода контекста (Записать): В данной транзакции уже происходили ошибки!
Таким образом, организация вложенных транзакций в 1С абсолютно бессмысленна.
Теперь вернемся к варианту, где мы записывали товар и цену для него. Если у нас при выполнении транзакции произойдет ошибка, то будет трудно понять в какой момент она произошла — при записи товара или при записи цены, поскольку и то и другое происходит в рамках одной попытки. Чтобы определить место возникновения ошибки, нам надо каждую операцию записи заключить в свою собственную попытку и при этом избежать вложенных транзакций. Для этого введем булеву переменную Отказ и в зависимости от ее значения в конце всех операций будем фиксировать или отменять транзакцию.
&НаСервереБезКонтекста
Процедура ВыполнитьТранзакциюНаСервере()
// Начинаем транзакцию
Отказ = Ложь;
НачатьТранзакцию();
// Пытаемся записать товар
Попытка
Товар = Справочники.Товары.СоздатьЭлемент();
Товар.Наименование = "Дырокол";
Товар.Записать();
Исключение
Отказ = Истина;
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Ошибка при записи товара";
Сообщение.Сообщить();
КонецПопытки;
// Пытаемся записать цену
Попытка
НаборЗаписей = РегистрыСведений.Цена.СоздатьНаборЗаписей();
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = ТекущаяДата();
НоваяЗапись.Товар = Товар.Ссылка;
НоваяЗапись.Сумма = 100;
НаборЗаписей.Записать();
Исключение
Отказ = Истина;
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Ошибка при записи цены";
Сообщение.Сообщить();
КонецПопытки;
// Фиксируем или отменяем транзакцию
Если НЕ Отказ Тогда
ЗафиксироватьТранзакцию();
Иначе
ОтменитьТранзакцию();
КонецЕсли;
КонецПроцедуры
Аналогичным образом можно поступить и когда мы перебираем и записываем какие-либо данные в цикле. В этом случае мы сможем получить перечень всех данных с ошибками, если такие будут.
Добавить комментарий