Декларативная модель транзакций в EJB

Наиболее часто применяемой моделью транзакций на платформе Java является декларативная модель, также известная как модель транзакций, управляемых контейнером (Container Managed Transactions – CMT). При работе с этой моделью контейнер самостоятельно начинает, подтверждает и откатывает транзакции. Задачей разработчика является только описание поведения транзакций.

Для описания поведения транзакций в Spring Framework и EJB 3.0 используются аннотации. В Spring аннотация называется @Transactional, а в EJB 3.0 – @TransactionAttribute. При использовании декларативной модели контейнер не будет автоматически откатывать транзакции при выбросе контролируемых исключений. Разработчику следует указать, когда и в каких именно случаях выброс таких исключений должен приводить к откату транзакции. В Spring Framework это делается при помощи свойства rollbackFor аннотации @Transactional. В EJB для этого служит метод setRollbackOnly() класса SessionContext.Пример использования декларативной модели транзакций в EJB приведен в листинге 1.

Листинг 1. Пример использования декларативных транзакций в EJB 3.0

@Stateless public class TradingServiceImpl implements TradingService { @PersistenceContext(unitName=»trading») EntityManager em; @Resource SessionContext ctx; @TransactionAttribute(TransactionAttributeType.REQUIRED) public void processTrade(TradeData trade) throws Exception { try { em.persist(trade); AcctData acct = em.find(AcctData.class, trade.getAcctId()); double tradeValue = trade.getPrice() * trade.getShares(); double currentBalance = acct.getBalance(); if (trade.getAction().equals(«BUY»)) { acct.setBalance(currentBalance — tradeValue); } else { acct.setBalance(currentBalance + tradeValue); } } catch (Exception up) { ctx.setRollbackOnly(); throw up; } } }
В листинге 1 иллюстрируется работа с декларативной моделью транзакций в Spring Framework.

Листинг 2. Пример использования декларативных транзакций в Spring

public class TradingServiceImpl { @PersistenceContext(unitName=»trading») EntityManager em; @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class) public void processTrade(TradeData trade) throws Exception { em.persist(trade); AcctData acct = em.find(AcctData.class, trade.getAcctId()); double tradeValue = trade.getPrice() * trade.getShares(); double currentBalance = acct.getBalance(); if (trade.getAction().equals(«BUY»)) { acct.setBalance(currentBalance — tradeValue); } else { acct.setBalance(currentBalance + tradeValue); } } }

Атрибуты транзакций

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *


шесть − = 4

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>