Содержание


Стратегии работы с транзакциями

Распространенные ошибки

Не забывайте о распространенных ошибках при реализации транзакций на платформе Java

Comments

Серия контента:

Этот контент является частью # из серии # статей: Стратегии работы с транзакциями

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Стратегии работы с транзакциями

Следите за выходом новых статей этой серии.

Основной причиной использования транзакций в приложениях является обеспечение высокой степени целостности и согласованности данных. Если вас не заботит качество ваших данных, то нет причин думать на тему применения транзакций, поскольку на платформе Java они могут серьезно снизить производительность, привести к проблемам, связанным с блокировками, а также повысить сложность приложения в целом.

Разработчики, не желающие применять транзакции, поступают так на свой страх и риск. Практически во всех бизнес-приложениях необходимо поддерживать высокое качество данных. Один только финансовый инвестиционный бизнес теряет десятки миллиардов долларов из-за неудачных торгов, причем второй по распространенности причиной этого являются некачественные данные (см. раздел Ресурсы). Отсутствие транзакций является не единственным фактором, отвечающим за потерю качества данных, но тем не менее можно смело утверждать, что недостаточная и отсутствующая вовсе поддержка транзакций стоит финансовой индустрии миллиардов долларов.

Одной из проблем является плохая осведомленность разработчиков о транзакциях вообще. Мне часто приходится слышать утверждения наподобие такого: "В наших приложениях не бывает сбоев, поэтому поддержка транзакций не требуется". В чем-то это верно. Я встречал приложения, которые или очень редко, или никогда не выбрасывают исключения. Благодаря хорошо написанному коду, процедурам валидации, а также циклу тестирования и проверке покрытия кода тестами, им удается избежать потерь производительности и повышенной сложности, связанной с обработкой транзакций. Однако подобный взгляд на необходимость транзакций является несколько однобоким, поскольку принимает во внимание только одно их свойство – атомарность. Атомарность гарантирует, что все изменения в базе данных являются единым целым и либо все выполняются, либо все отменяются. Однако координированное выполнение и откат изменений – это лишь один из аспектов транзакционности. Другим аспектом является изоляция транзакций, означающая, что процедуры работы с данными изолируются друг от друга. В отсутствие должной изоляции одним процедурам могут стать видимы изменения, сделанные другими, параллельно выполняющимися процедурами, даже если последние еще не завершились. В результате бизнес-решения могут приниматься на основании неполных данных, которые, в частности, могут привести к срыву торгов и другим негативным и дорого обходящимся последствиям.

Итак, учитывая то, насколько дорого могут обойтись некачественные данные, а также обладая базовым представлением о важности и необходимости транзакций, вам необходимо научиться их использовать и решать связанные с ними потенциальные проблемы. Допустим, вам удалось "продавить" решение о добавлении транзакций в ваше приложение. Именно в этот момент часто начинаются проблемы. В частности, на платформе Java транзакции зачастую работают не так, как было обещано по причинам, которые будут рассмотрены ниже. Я покажу на примерах некоторые из распространенных ошибок при работе с транзакциями, которые приходится наблюдать из раза в раз, причем, как правило, в уже введенных в эксплуатацию приложениях.

Несмотря на то, что в большей части примеров к этой статье используется Spring Frameworks (версия 2.5), понятия при работе с транзакциями остались такими же, как и в спецификации EJB 3.0. В большинстве случаев достаточно заменить аннотацию @Transactional в Spring на аннотацию @TransactionAttribute из спецификации EJB 3.0. В тех случаях, когда Spring и EJB расходятся (концептуально и технически), будут приведены примеры кода, соответствующие обеим инфраструктурам.

Ошибки, связанные с локальными транзакциями

Начать лучше всего с рассмотрения самого простого случая, а именно – локальных транзакций, также часто называемых транзакциями баз данных. В былые времена при работе с хранилищами данных (например, через JDBC) было принято оставлять обработку транзакций базе данных. В конце концов, это ее прямая обязанность, не так ли? Локальные транзакции идеально подходят для обработки логических единиц работы (logical units of work – LUW), выполняющих единичные операторы вставки, изменения или удаления данных. Например, рассмотрим простой фрагмент JDBC-кода, который добавляет торговый приказ в таблицу TRADE (листинг 1).

Листинг 1. Простая вставка записи в базу данных через JDBC
@Stateless
public class TradingServiceImpl implements TradingService {
   @Resource SessionContext ctx;
   @Resource(mappedName="java:jdbc/tradingDS") DataSource ds;

   public long insertTrade(TradeData trade) throws Exception {
      Connection dbConnection = ds.getConnection();
      try {
         Statement sql = dbConnection.createStatement();
         String stmt =
            "INSERT INTO TRADE (ACCT_ID, SIDE, SYMBOL, SHARES, PRICE, STATE)"
          + "VALUES ("
          + trade.getAcct() + "','"
          + trade.getAction() + "','"
          + trade.getSymbol() + "',"
          + trade.getShares() + ","
          + trade.getPrice() + ",'"
          + trade.getState() + "')";
         sql.executeUpdate(stmt, Statement.RETURN_GENERATED_KEYS);
         ResultSet rs = sql.getGeneratedKeys();
         if (rs.next()) {
            return rs.getBigDecimal(1).longValue();
         } else {
            throw new Exception("Trade Order Insert Failed");
         }
      } finally {
         if (dbConnection != null) dbConnection.close();
      }
   }
}

В коде работы через JDBC, приведенном в листинге 1, нет никакой логики обработки транзакций, хотя они и сохраняют торговые приказы в таблице TRADE в базе данных. В данном случае за выполнение транзакций отвечает сама база данных.

Все это работает хорошо в случае выполнения единственной операции в LUW. Однако, допустим, что необходимо обновить состояние счета в момент добавления заказа в базу данных, как показано в листинге 2.

Листинг 2. Выполнение нескольких операций изменения данных внутри одного метода
public TradeData placeTrade(TradeData trade) throws Exception {
   try {
      insertTrade(trade);
      updateAcct(trade);
      return trade;
   } catch (Exception up) {
      //запись в журнал ошибок
      throw up;
   }
}

В этом примере методы insertTrade() и updateAcct() используют JDBC, не включая их в одну транзакцию. После выполнения метода insertTrade() торговый приказ был сохранен в базе данных, и соответствующая транзакция была подтверждена. Если вызов метода updateAcct(), то приказ останется в таблице TRADE что будет означать несогласованность базы данных. Если бы метод placeTrade() использовал транзакции, то обе операции являлись бы частью единой LUW, и заказ на торги был бы отменен в случае сбоя при обновлении счета.

Подобные примеры кода, непосредственно работающего с JDBC, стали встречаться реже в связи с распространением Java-инфраструктур для работы с хранилищами данных, таких как Hibernate, TopLink и Java Persistence API (JPA). Как правило, используются библиотеки для объектно-реляционного отображения (ORM), которые облегчают жизнь разработчикам, скрывая весь нелицеприятный код JDBC за вызовами нескольких методов. Например, для того, чтобы выполнить ту же операцию по вставке заказа, JDBC-код из листинга 1 можно заменить на JPA-код, показанный в листинге 3, предварительно отобразив класс TradeData на таблицу TRADE.

Листинг 3. Пример простой вставки данных при помощи JPA
public class TradingServiceImpl {
    @PersistenceContext(unitName="trading") EntityManager em;

    public long insertTrade(TradeData trade) throws Exception {
       em.persist(trade);
       return trade.getTradeId();
    }
}

Обратите внимание, что в листинге 3 вызывается метод persist() класса EntityManager для вставки торгового приказа. Все просто? Отнюдь. Этот код не вставит запись в таблицу TRADE, как этого можно было бы ожидать, но не выбросит и исключения. Он просто вернет значение 0 в качестве первичного ключа приказа, не изменяя состояния базы данных. Это один из первых серьезных подводных камней при обработке транзакций: инфраструктурам ORM требуются транзакции для синхронизации содержимого кэша объектов и базы данных. Генерация SQL-кода и изменение базы данных вследствие выполнения нужного оператора (insert, update или delete) выполняется только при подтверждении транзакции. В отсутствие транзакции некому дать сигнал ORM о том, что надо сгенерировать код SQL и выполнить изменения в базе данных, поэтому метод просто завершается, ничего не сделав, даже не выбросив исключения. При работе с ORM вы обязаны использовать транзакции. Больше нельзя полагаться на базу данных в вопросах управления соединения и подтверждения изменений.

Эти простые примеры ясно показывают, что транзакции необходимы для поддержки целостности и согласованности данных. Однако пока мы затронули лишь верхушку айсберга в вопросах сложности и потенциальных ошибок при реализации выполнения транзакций на платформе Java.

Ошибки, связанные с аннотацией @Transactional в Spring Framework

Итак, выполнив код, приведенный в листинге 3, мы убедились, что в отсутствие транзакций метод persist() работает не так, как предполагалось. Выполнив поверхностный поиск в Интернет, можно найти ряд страниц, на которых объясняется, что при работе с Spring Framework следует использовать аннотацию @Transactional. Добавьте ее в код, как показано в листинге 4.

Листинг 4. Пример использования аннотации @Transactional
public class TradingServiceImpl {
   @PersistenceContext(unitName="trading") EntityManager em;

   @Transactional
   public long insertTrade(TradeData trade) throws Exception {
      em.persist(trade);
      return trade.getTradeId();
   }
}

Однако этот вариант программы по-прежнему не работает по причине того, что следует явно сообщить Spring Framework о факте использования аннотаций для управления транзакциями. Эту проблему иногда сложно обнаружить, если код полностью не покрыт юнит-тестами. Из-за этого некоторые разработчики попросту добавляют логику управления транзакциями в конфигурационные файлы Spring вместо использования аннотаций.

При использовании аннотации @Transactional в Spring следует добавить следующую строку в конфигурационный файл Spring:

<tx:annotation-driven transaction-manager="transactionManager"/>

В свойстве transaction-manager хранится ссылка на менеджер транзакций, определенный в конфигурационном файле Spring. Эта строка указывает Spring, что при применении перехватчика транзакций следует использовать аннотацию @Transactional. Без нее данная аннотация игнорируется, в результате чего транзакции в коде не используются вовсе.

Добиться того, чтобы аннотация @Transactional в листинге 4 заработала – это только начало. Обратите внимание, что в листинге 4 эта аннотация используется без каких бы то ни было параметров. Мне встречалось множество разработчиков, которые применяли аннотацию @Transactional, не разобравшись полностью в том, что она делает. Например, какой режим распространения используется при отсутствии параметров аннотации? Каково значение флага "только чтение" (read-only)? Какой уровень изоляции используется? Еще более важным является вопрос о том, когда транзакция должна откатываться. Понимание того, как следует применять данную аннотацию, очень важно для организации правильной поддержки транзакций в вашем приложении. Ответы звучат следующим образом: при использовании аннотации @Transactional без параметров режимом распространения является REQUIRED, значением атрибута "только чтение" – false, уровень изоляции соответствует уровню изоляции по умолчанию для базы данных (как правило, это READ_COMMITTED), и транзакция не будет откатываться в случае контролируемых исключений (checked exception).

Ошибки, связанные с флагом "только чтение" аннотации @Transactional

Одной из ошибок, которые мне часто приходится наблюдать, является некорректное использование признака "только чтение" аннотации @Transactional в Spring. Попробуйте ответить на следующий вопрос: что делает аннотация @Transactional (листинг 5) в случае установки этого флага в значение true и режима распространения SUPPORTS при использовании стандартного JDBC-кода для сохранения данных?

Листинг 5. Пример использования флага "только чтение" и режима распространения SUPPORTS с JDBC
@Transactional(readOnly = true, propagation=Propagation.SUPPORTS)
public long insertTrade(TradeData trade) throws Exception {
   //Работа через JDBC...
}

Выберите из следующих вариантов правильный вариант того, что произойдет при выполнении метода insertTrade() из листинга 5.

  • Выбросит исключение, поскольку подключение разрешает только операции чтения.
  • Корректно вставит торговый приказ и подтвердит сохранение данных.
  • Ничего не сделает, поскольку уровнем распространения является SUPPORTS.

Сдаетесь? Правильным является ответ "В". Торговый приказ корректно сохраняется в базе данных несмотря на флаг "только чтение" и режим распространения SUPPORTS. Как это возможно? Дело в том, что из-за режима распространения SUPPORTS новая транзакция не начинается, поэтому в методе используется локальная транзакция (т.е. транзакция базы данных). Флаг "только чтение" применяется только в случае начала новой транзакции, а в этом примере он попросту игнорируется.

Тогда возникает вопрос, что будет делать аннотация @Transactional в листинге 6 с установленным флагом "только чтение" и режимом распространения REQUIRED?

Листинг 6. Пример использования флага "только чтение" и режима распространения REQUIRED в JDBC
@Transactional(readOnly = true, propagation=Propagation.REQUIRED)
public long insertTrade(TradeData trade) throws Exception {
   //Работа через JDBC...
}

Как вы думаете, что из следующего произойдет при выполнении метода insertTrade(), приведенного в листинге 6:

  • Метод выбросит исключение, поскольку подключение разрешает только операции чтения.
  • Метод корректно вставит торговый приказ и подтвердит сохранение данных.
  • Ничего не произойдет, поскольку выставлен флаг "только чтение".

На этот вопрос несложно ответить, принимая во внимание объяснение предыдущей ситуации. Правильным ответом является "А". Метод выбросит исключение, говорящее о том, что производится попытка изменения данных через соединение, разрешающее только операции чтения. В данном случае начинается новая транзакция (из-за режима распространения REQUIRED), поэтому соединение помечается как "только для чтения". Таким образом, при попытке выполнить оператор SQL вы получите исключение, указывающее на это.

Странной особенностью флага "только чтение" является то, что он вступает в силу только с началом новой транзакции. Зачем надо начинать транзакцию, если речь идет только о чтении данных? Разумеется, этого делать не нужно. Начало транзакции при выполнении операции чтения всего лишь добавит лишних накладных расходов в обрабатывающий поток, а также может привести к появлению разделяемых блокировок чтения в базе данных (это зависит от типа используемой базы данных и уровня изоляции). Таким образом, флаг "только чтение" оказывается несколько бессмысленным при работе через JDBC и приводит к увеличению накладных расходов из-за выполнения необязательной транзакции.

А что будет происходить при использовании ORM-инфраструктуры? Попробуйте угадать результат работы аннотации @Transactional в случае, если метод insertTrade() вызывался при использовании Hibernate и JPA (листинг 7).

Листинг 7. Пример использования флага "только чтение" и режима распространения REQUIRED в JPA
@Transactional(readOnly = true, propagation=Propagation.REQUIRED)
public long insertTrade(TradeData trade) throws Exception {
   em.persist(trade);
   return trade.getTradeId();
}

Выберите правильный вариант того, что произойдет при вызове метода insertTrade() из листинга 7.

  • Метод выбросит исключение, поскольку подключение разрешает только операции чтения.
  • Метод корректно вставит торговый приказ и подтвердит сохранение данных.
  • Ничего не произойдет, поскольку выставлен флаг readOnly (только чтение).

Ответ на этот вопрос не так прост. В некоторых ситуациях ответом будет "С", однако в большинстве случаев (в частности, при использовании JPA) – "В". Торговый приказ благополучно добавляется в базу данных без всяких ошибок. Секундочку, ведь предыдущий пример ясно продемонстрировал, что при использовании режима распространения REQUIRED будет выброшено исключение. Это так при работе через JDBC, однако при использовании инфраструктуры ORM флаг "только чтение" работает несколько по-другому. В момент вставки записи инфраструктура будет сначала обращаться к базе данных для генерации ключевого значения. В некоторых ORM режим сброса данных (flush) будет равен MANUAL, поэтому, если ключ не сгенерирован, то вставка выполнена не будет. То же самое справедливо для изменения данных. Однако другие ORM, в частности TopLink, всегда выполняют вставку и изменение данных при установленном флаге "только чтение". Несмотря на то, что это поведение определяется конкретной инфраструктурой ORM и ее версией, необходимо помнить, что нет гарантии, что операции вставки или изменения данных не будут выполнены при установленном флаге "только чтение", особенно при использовании JPA, поскольку ее поведение не зависит от компании-разработчика.

Итак, мы подходим к еще одной ошибке, которую мне часто приходится наблюдать. Приняв во внимание все, что было рассказано выше, как вы думаете, что произойдет при выполнении кода в листинге 8, где единственным атрибутом аннотации @Transactional является флаг "только чтение"?

Листинг 8. Пример использования флага "только чтение" в JPA
@Transactional(readOnly = true)
public TradeData getTrade(long tradeId) throws Exception {
   return em.find(TradeData.class, tradeId);
}

Что из следующего произойдет при выполнении метода getTrade(), приведенного в листинге 8.

  • Транзакция начнется, будет выбран торговый приказ, затем транзакция будет подтверждена.
  • Торговый приказ будет получен без старта новой транзакции.

Правильным ответом является "А": транзакция будет начата и подтверждена. Не забывайте, что режимом распространения по умолчанию для аннотации @Transactional является REQUIRED. Это означает, что транзакция будет начата даже в том случае, когда это не необходимо (см. заметку Никогда не говори "никогда"). В зависимости от используемой базы данных, это может приводить к необязательным разделяемым блокировкам, которые могут повлечь за собой взаимные блокировки. Кроме того, начало и завершение транзакции приводят к увеличению продолжительности обработки и повышенному расходованию ресурсов. Итак, можно сделать следующий вывод: при использовании ORM флаг "только чтение", как правило, бесполезен, и в большинстве случаев игнорируется. Однако если вы все же хотите его использовать, то обязательно установите режим распространения SUPPORTS, чтобы избежать запуска транзакций (листинг 9).

Листинг 9. Пример использования флага "только чтение" и режима распространения SUPPORTS при выборке данных
@Transactional(readOnly = true, propagation=Propagation.SUPPORTS)
public TradeData getTrade(long tradeId) throws Exception {
   return em.find(TradeData.class, tradeId);
}

Еще лучше вообще не использовать аннотацию @Transactional при чтении данных из базы (листинг 10).

Листинг 10. Выборка данных без аннотации @Transactional
public TradeData getTrade(long tradeId) throws Exception {
   return em.find(TradeData.class, tradeId);
}

Ошибки, связанные с атрибутом транзакций REQUIRES_NEW

При работе со Spring Framework или EJB использование атрибута транзакций REQUIRES_NEW может привести к негативным результатам, в частности, к искажению данных или потере согласованности. Этот атрибут всегда запускает новую транзакцию при начале выполнения метода вне зависимости от того, существует ли ранее начатая транзакция. Многие разработчики некорректно используют атрибут REQUIRES_NEW, предполагая, что это правильный способ гарантии запуска транзакции. Рассмотрим два метода, показанные в листинге 11.

Листинг 11. Пример использования атрибута REQUIRES_NEW
@Transactional(propagation=Propagation.REQUIRES_NEW)
public long insertTrade(TradeData trade) throws Exception {...}

@Transactional(propagation=Propagation.REQUIRES_NEW)
public void updateAcct(TradeData trade) throws Exception {...}

Заметьте, что оба метода в листинге 11 являются открытыми, а это, в частности, означает, что они могут быть вызваны независимо друг от друга. Проблемы, связанные с атрибутом REQUIRES_NEW, возникают в случае, если методы вызываются внутри одной логической единицы работы (LUW) путем межсервисного или программного взаимодействия. Например, допустим, что метод updateAcct() в листинге 11 может вызываться независимо от других методов, однако в некоторых случаях он может быть вызван внутри метода insertTrade(). Если при этом будет выброшено исключение после вызова updateAcct(), то добавление торгового приказа будет отменено, но изменения баланса счета будут сохранены в базу данных (листинг 12).

Листинг 12. Несколько операций изменения данных при использовании атрибута REQUIRES_NEW
@Transactional(propagation=Propagation.REQUIRES_NEW)
public long insertTrade(TradeData trade) throws Exception {
   em.persist(trade);
   updateAcct(trade);
   //В этом месте возникает исключение.
   //Добавление приказа отменяется, а изменение баланса - нет.
   ...
}

Это происходит потому, что новая транзакция начинается внутри метода updateAcct() и, следовательно, она подтверждается после его благополучного завершения. Использование атрибута транзакции REQUIRES_NEW означает, что выполнение существующей транзакции (если таковая есть) прерывается, и начинается новая транзакция. После завершения метода новая транзакция подтверждается, а старая – возобновляется.

Вследствие такого поведения атрибут REQUIRES_NEW должен использоваться только в случае, если операция с базой данных должна быть подтверждена вне зависимости от результатов внешней транзакции. Например, допустим, что каждый торговый приказ должен быть отражен в базе данных аудита. Эта информация должна быть сохранена вне зависимости от того, был ли приказ успешен или завершился неудачей ввиду ошибок валидации, отсутствия необходимых средств или по любой другой причине. Если в этой ситуации не использовать атрибут REQUIRES_NEW для метода аудита, то сохранение аудиторской информации будет отменено вместе с сохранением самого приказа. Данный атрибут гарантирует, что информация для аудита будет сохранена в базе данных вне зависимости от успешности исходной транзакции. Таким образом, следует всегда использовать атрибуты MANDATORY или REQUIRED вместо REQUIRES_NEW, кроме особых случаев, схожих с примером сохранения аудиторской информации.

Ошибки, связанные с откатом транзакций

Я приберег наиболее распространенную ошибку напоследок. К сожалению, мне приходится видеть ее в рабочем коде в более чем половине случаев. Мы сначала рассмотрим ситуацию с использованием Spring Framework, а затем перейдем к EJB 3.

До этого момента все примеры кода выглядели примерно как в листинге 13.

Листинг 13. Выполнение операций без возможности отката
@Transactional(propagation=Propagation.REQUIRED)
public TradeData placeTrade(TradeData trade) throws Exception {
   try {
      insertTrade(trade);
      updateAcct(trade);
      return trade;
   } catch (Exception up) {
      //Запись ошибки в журнал
      throw up;
   }
}

Допустим, что на счете недостаточно средств для покупки ценных бумаг, либо он не настроен должным образом для их покупки или продажи. В этом случае метод выбросит контролируемое исключение, например FundsNotAvailableException. Сохранится ли в базе данных торговый приказ, или логическая единица работы будет полностью отменена? Как ни странно, но при выбросе контролируемого исключения в Spring Framework или EJB транзакция подтвердит все неподтвержденные к этому моменту изменения. Применительно к листингу 13 это означает, что при возникновении исключения в методе updateAcct() торговый приказ будет сохранен, но соответствующие изменения в балансе счета произведены не будут.

Это, возможно, главная угроза целостности и согласованности данных при работе с транзакциями. Исключения времени выполнения (или неконтролируемые исключения) автоматически приводят к откату всей логической единицы работы, а контролируемые исключения – нет. Таким образом, код в листинге 13 оказывается бессмысленным с точки зрения обработки транзакций. Может показаться, что транзакции используются для обеспечения атомарности и согласованности, но это лишь видимость.

Подобное поведение транзакций может показаться странным, однако у него есть убедительное обоснование. Во-первых, не все контролируемые исключения являются ошибками, они также могут использоваться для отправки уведомлений или передачи управления по заданному условию. Однако основной причиной является то, что прикладной код может сам обрабатывать некоторые типы контролируемых исключений, после чего транзакция должна быть подтверждена. Например, рассмотрим следующий сценарий: вы пишете приложение для книжного Интернет-магазина. Для завершения заказа приложение должно отправить клиенту подтверждение по электронной почте. Если сервер e-mail неработоспособен, то будет выброшено контролируемое исключение SMTP, сигнализирующее о невозможности отправки сообщения. Если бы контролируемые исключения автоматически приводили к откату транзакций, то весь заказ на покупку книг был бы отменен только потому, что сервер не смог отправить почту. Если же транзакции не откатываются, то вы сами можете перехватить такое исключение и обработать его нужным образом (например, поместить сообщение в очередь на отправку), после чего подтвердить остальные изменения, касающиеся заказа.

Если вы используете декларативную модель транзакций (она более подробно описывается во второй статье этой серии), то необходимо указать, как именно контейнер или инфраструктура должны обрабатывать контролируемые исключения. В Spring Framework это делается при помощи параметра rollbackFor в аннотации @Transactional (листинг 14).

Листинг 14. Добавление возможности отката транзакции при работе с Spring
@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
public TradeData placeTrade(TradeData trade) throws Exception {
   try {
      insertTrade(trade);
      updateAcct(trade);
      return trade;
   } catch (Exception up) {
      //Запись ошибки в журнал
      throw up;
   }
}

Обратите внимание на использование параметра rollbackFor в аннотации @Transactional. Значением этого параметра может быть либо класс исключения, либо массив подобных классов. Кроме того, можно использовать параметр rollbackForClassName для задания имен исключений в строковом виде. Существует также обратный параметр noRollbackFor для указания того, что любое исключение, кроме заданных, должно приводить к откату транзакции. Как правило, большинство разработчиков указывают в качестве значения Exception.class, что означает, что все исключения, выбрасываемые данным методом, должны приводить к откату транзакции.

В том, что касается отката транзакций, поведение EJB несколько отличается от Spring Framework. Атрибут @TransactionAttribute, описанный в спецификации EJB 3.0, не предоставляет директив для управления откатом транзакции. Вместо этого вы должны использовать метод SessionContext.setRollbackOnly() для того, чтобы пометить транзакцию как подлежащую откату (листинг 15).

Листинг 15. Добавление возможности отката транзакций при работе с EJB
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public TradeData placeTrade(TradeData trade) throws Exception {
   try {
      insertTrade(trade);
      updateAcct(trade);
      return trade;
   } catch (Exception up) {
      //Запись ошибки в журнал
      sessionCtx.setRollbackOnly();
      throw up;
   }
}

После вызова метода setRollbackOnly() уже ничего изменить нельзя: откат неминуемо произойдет после завершения выполнения метода, который начал транзакцию. В следующих статьях будут описаны стратегии обработки транзакций, которые помогут вам решить, когда и где использовать директивы отката, а также в каких случаях следует использовать атрибуты транзакции REQUIRED и MANDATORY.

Заключение

Код, который приходится писать для выполнения транзакций в Java, не представляет особых сложностей, но при этом бывает непросто решить, как его использовать и конфигурировать. Вследствие этого с реализацией поддержки транзакционности в приложениях Java связано множество распространенных ошибок (в том числе некоторые реже встречающиеся проблемы, которые не вошли в эту статью). Главной проблемой является то, что о некорректной работе транзакций в вашем приложении вы не узнаете ни от ошибок компилятора, ни от аварийного завершения программы на этапе выполнения. Более того, реализация поддержки транзакций не ограничивается лишь написанием кода, как это предполагалось в истории, описанной в заметке "Лучше поздно, чем никогда". Это также требует существенных усилий на этапе проектирования приложения. Остальные статьи серии "Стратегии работы с транзакциями" помогут вам в проектировании эффективных стратегий работы с транзакциями при решении различных задач – от создания простых приложений до реализации высокопроизводительных транзакционных систем.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=676754
ArticleTitle=Стратегии работы с транзакциями: Распространенные ошибки
publish-date=06022011