ATUM обратиться по имени
Понедельник, 29 Апреля 2013 г. 15:44 (ссылка)
Кроме директив отката, нужно указать также транзакционный атрибут, который определяет поведение транзакции. Платформа Java поддерживает шесть типов транзакционных атрибутов, независимо от того, используете вы EJB или Spring Framework:
Required
Mandatory
RequiresNew
Supports
NotSupported
Never
Описывая каждый из них, я буду использовать фиктивный метод methodA(), к которому и будет применяться транзакционный атрибут.
Если для метода methodA() указан транзакционный атрибут Required, и methodA() вызывается из существующей транзакции, будет использоваться область видимости существующей транзакции. В противном случае methodA() начнет новую транзакцию. Если транзакцию начал methodA(), то и завершать ее (фиксировать или откатывать) должен тоже methodA(). Это чаще всего применяемый транзакционный атрибут, используемый по умолчанию как в EJB 3.0, так и в Spring. К сожалению, во многих случаях он используется некорректно, что приводит к проблемам с целостностью и непротиворечивостью данных. Для каждой из транзакционных стратегий я в следующих статьях серии более подробно рассмотрю использование этого атрибута.
Если для метода methodA() указан транзакционный атрибут Mandatory, и methodA() вызывается из области видимости существующей транзакции, будет использоваться область видимости существующей транзакции. Однако если methodA() вызывается вне контекста транзакции, будет сгенерировано исключение TransactionRequiredException, указывающее, что транзакция уже должна существовать до вызова methodA(). Этот транзакционный атрибут используется в транзакционной стратегии Client Orchestration, описанной в следующем разделе этой статьи.
Интересен транзакционный атрибут RequiresNew. Я встречаюсь с неверным использованием или пониманием этого атрибута чаще, чем с верным. Если для methodA() указан транзакционный атрибут RequiresNew, и methodA() вызывается изнутри или извне контекста транзакции, methodA() всегда начинает (и заканчивает) новую транзакцию. Это значит, что если methodA() вызывается из контекста другой транзакции (например, Transaction1), Transaction1 будет приостановлена, и будет начата новая транзакция (Transaction2). После завершения methodA()Transaction2 будет зафиксирована или отменена, и возобновится исполнение Transaction1. Это, очевидно, нарушает свойства транзакций ACID (ACID – атомарность, непротиворечивость, изоляция, надежность), особенно атомарность. Другими словами, все обновления БД в этом случае уже не содержатся в одной единице работы. В случае отката Transaction1 изменения, внесенные Transaction2, сохранятся. В таком случае, зачем нужен такой транзакционный атрибут? Как было показано в первой статье серии, этот транзакционный атрибут нужно использовать только при операциях над БД (например, аудите или логировании), независимых от нижележащих транзакций (в данном случае Transaction1).
Транзакционный атрибут Supports – еще один, который, я считаю, большинство разработчиков не совсем понимает или оценивает. Если для methodA() указан транзакционный атрибут Supports, и methodA() вызывается в контексте существующей транзакции, methodA() будет исполняться в контексте существующей транзакции. Если же methodA() вызывается вне контекста транзакции, новая транзакция не начинается. Этот атрибут в основном используется при операциях чтения из БД. Почему бы в этом случае не использовать транзакционный атрибут NotSupported (описанный в следующем абзаце)? В конце концов, этот атрибут гарантирует, что метод будет выполняться без использования транзакций. Ответ прост. Исполнение запроса в контексте существующей транзакции приведет к считыванию данных из лога транзакций БД (проще говоря, измененных данных), а при его исполнении вне контекста транзакции будут считаны неизмененные данные из таблицы. Например, если вы хотите вставить в таблицу Trade новый заказ и затем (в той же транзакции) получить список заказов, незафиксированный заказ будет присутствовать в списке. Но если бы вы использовали транзакционный атрибут NotSupported, запрос обращался бы к таблице, а не к логу транзакций. Таким образом, вы не увидели бы в списке незафиксированного заказа. Это не обязательно плохо, все зависит от бизнес-логики и конкретной ситуации.
Транзакционный атрибут NotSupported указывает, что вызываемый метод не будет использовать или начинать транзакцию, независимо от наличия таковой. Если для methodA() указан транзакционный атрибут NotSupported, и methodA() вызывается в контексте существующей транзакции, транзакция приостанавливается до окончания выполнения метода. После окончания выполнения метода исходная транзакции возобновляется. Есть всего несколько случаев использования этого транзакционного атрибута, и в основном они относятся к использованию хранимых процедур. Если вы пытаетесь вызвать хранимую процедуру из контекста существующей транзакции, и хранимая процедура содержит BEGIN TRANS или, в случае Sybase, исполняется в unchained-режиме, будет выдано исключение, указывающее, что новая транзакция не может быть начата в случае наличия уже существующей (проще говоря, что вложенные транзакции не поддерживаются). Почти все контейнеры используют Java Transaction Service (JTS) как реализацию транзакций для JTA по умолчанию. Это именно JTS – а не платформа Java как таковая – не поддерживает вложенных транзакций. Если нельзя изменить хранимую процедуру, можно использовать атрибут NotSupported, чтобы прервать существующую транзакцию и избежать фатального исключения. При этом, конечно, вы уже не сможете внести атомарные изменения в БД в той же LUW. Это компромисс, но он может быстро вытащить вас из затруднительной ситуации.
Транзакционный атрибут Never, возможно, самый интересный из всех. Он работает так же, как NotSupported, но с одной существенной разницей. Если при вызове метода с транзакционным атрибутом Never уже существует контекст транзакции, генерируется исключение, указывающее, что при вызове этого метода транзакции недопустимы. Единственный случай использования этого атрибута, который я смог придумать – тестирование. Он предоставляет простой и быстрый способ проверки наличия транзакции на момент вызова конкретного метода. Если вы используете транзакционный атрибут Never и получаете исключение при вызове проверяемого метода, значит, транзакция существует, и наоборот. Это хороший способ проверить, что ваша транзакционная стратегия работает.