Почему CMT фиксируется при выходе из метода EJB, когда атрибут транзакции "Обязательный"?

Я постоянно обнаруживаю, что моя уже существующая транзакция фиксируется внутри любого метода EJB-метки @ejb.transactiontype="Required", Это может быть правильно?

Я ожидаю, что EJB, "требующий" транзакции, означает: если она уже есть, она вежливо оставит ее незафиксированной, когда это будет сделано, чтобы тот, кто вызвал begin(), мог продолжать использовать ее для дальнейших операций перед вызовом. commit() или же rollback(), [Конечно, если бы не было транзакции, то метод EJB вызвал бы оба begin() а также commit() / rollback().]

Мои ожидания неверны, или я должен искать ошибку конфигурации?

Может быть уместно добавить, что я использую Hibernate 3 внутри EJB. Я получаю UserTransaction перед вызовом метода EJB. Сгенерированная EJB оболочка вызывает ServerTransaction.commit() на выходе, который Hibernate подключается и использует возможность закрыть свою сессию. Ошибка, которую я получаю, является исключением отложенной загрузки Hibernate, потому что сеанс закрывается, когда я пытаюсь получить доступ к получателям на сохраненном в Hibernate объекте. Технически я не уверен на 100% ServerTransaction.commit() Я заметил обязательно совершил UserTransaction Я начал (возможно ServerTransaction.commit() не всегда ли на самом деле выполняется "реальный" коммит?), но если нет, то на каком основании Hibernate закрывает сессию?

Обновление: я полагаю, что мои предположения выше были правильны, но мои наблюдения были немного неправильными. Смотрите ниже мой собственный ответ.

4 ответа

Решение

При ближайшем рассмотрении обнаруживается иной ответ, чем предложенный выше. На самом деле я вижу, что запущенная мной UserTransaction остается открытой, но CMT создала новую транзакцию при входе в метод EJB, несмотря на атрибут "Required".

Я считаю, что это происходит, потому что я нарушил правила.:) Вы не должны обращаться к API UserTransaction при использовании CMT. CMT счастливо проигнорировала мою UserTransaction и начала свою собственную, заняв свое место в качестве арбитра всех границ транзакции. Так как он начал транзакцию, он также зафиксировал ее и, конечно, оставил мою UserTransaction нетронутой.

Мне кажется хрупким и глупым, возможно, наивное мнение, но, как я читал, кажется, соответствует "правилам". Я не знаю, почему CMT предпочитает не играть хорошо с UserTransactions, запущенными на более высоком уровне. Возможно, чтобы заставить разработчиков "делать правильные вещи J2EE" и создать еще один слой сессионных компонентов для обработки более широкого контекста транзакции. Это будет работать, потому что CMT будет управлять внешней транзакцией и, следовательно, будет хорошо с привлечением любых внутренних транзакций, и я считаю, что в таком случае "зонтичная" транзакция не будет зафиксирована внутренними EJB-компонентами; CMT будет ждать, пока внешняя транзакция завершится, а затем завершит все это. Это должно было бы, на самом деле.

Я не в настроении создавать больше сессионных EJB-компонентов в этом уже взорванном EJB-приложении, но, возможно, это единственное решение, кроме как вытащить CMT из целого ряда мест, которые я бы предпочел не трогать.

ТРЕБУЕТСЯ может быть злом

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

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

ОБЯЗАТЕЛЬНО и UserTransaction являются родственными душами

Ваше желание использовать UserTransaction очень хорошо и работает с CMT - нет разницы между транзакцией JTA, запущенной через UserTransaction, предоставленной контейнером, и транзакцией JTA, запущенной для вас контейнером.

Подход, который я бы порекомендовал, состоит в том, чтобы переключить все ОБЯЗАТЕЛЬНОЕ использование на ОБЯЗАТЕЛЬНО. С ОБЯЗАТЕЛЬНЫМ контейнером не начнут транзакции для вас. Вместо этого он защитит ваш компонент, гарантируя, что он не может быть вызван, если транзакция не выполняется. Это удивительная и недостаточно используемая функция. ОБЯЗАТЕЛЬНО - лучший друг любого, кто хочет создать по-настоящему детерминированное транзакционное приложение и применять его. С этой настройкой вы можете влюбиться в CMT.

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

Соображения

  • Сервлет или BMT EJB могут использовать транзакцию UserTransaction.
  • Бины CMT не могут использовать UserTransaction, но они могут участвовать в транзакции, запущенной UserTransaction (как отмечалось выше, транзакция JTA является транзакцией JTA).
  • Бины BMT не могут участвовать в существующей транзакции. Если вы вызываете bean-компонент BMT, когда транзакция уже выполняется, контейнер будет приостановлен контейнером до вызова bean-компонента BMT и возобновлен после завершения метода.
  • @Resource UserTransaction получит пользовательскую транзакцию через инъекцию
  • java:comp/UserTransaction получит пользовательскую транзакцию через поиск
  • @TransactionAttribute(MANDATORY) используемые на уровне класса будут влиять на методы этого точного класса (то есть fooClass.getDecaredMethods() методы). Методы суперклассов и подклассов по умолчанию будут @TransactionAttribute(REQUIRED) если эти классы также явно не аннотированы @TransactionAttribute(MANDATORY)
  • Если вам надоело звонить userTransaction.begin() а также userTransaction.commit() и делая связанную обработку исключений, рассмотрим @TransactionAttribute(REQUIRES_NEW), Границы вашей транзакции будут по-прежнему задокументированы и явны.
  • RuntimeException Значение, выброшенное из метода bean-компонента CMT, приведет к тому, что транзакция будет помечена для отката, даже если вы перехватите и обработаете исключение в вызывающем коде. использование @ApplicationException чтобы отключить это в каждом конкретном случае для пользовательских классов исключений во время выполнения.

Потеря контекста вашей транзакции

Пара вещей может заставить вашу текущую транзакцию остановиться, приостановить или иным образом не распространиться на вызываемый компонент.

  • Бины BMT останавливают распространение транзакции. Если текущая транзакция вызывает bean-компонент BMT, эта транзакция будет приостановлена ​​до вызова bean-компонента BMT и будет возобновлена ​​после возврата bean-компонента. Бины BMT могут быть источником транзакций, но не могут участвовать в существующих транзакциях. Если распространение происходит по таинственной причине, убедитесь, что в середине транзакции не было непреднамеренных вызовов bean-компонентов BMT.
  • Не используйте ничего, кроме предоставленной контейнером UserTransaction или предоставленных контейнером методов, таких как SessionContext.setRollbackOnly, для управления транзакциями. Использование "API для разграничения транзакций для конкретного менеджера ресурсов", такого как JPA EntityTransaction или же java.sql.Connection.commit() обойдет управление транзакциями.
  • Не начинайте свои собственные темы. Распространение транзакции происходит для каждого потока. В Java EE нет стандартных API, поддерживающих транзакцию, охватывающую несколько потоков. Если вы покинете поток, либо запустив свой собственный поток, либо используя @Asynchronous, вы оставите свою существующую транзакцию позади.

NClark, рассмотрим следующий код, который я запускал на GlassFish 3.1.1. Я надеюсь, что это поможет в любом случае:-)

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class ReusingTransaction {

   // It's BMT - we can't control Tx through context - must use...
   @Resource
   SessionContext sctx;

   // ... the UserTransaction instead.
   @Resource
   UserTransaction utx;

   // This CMT EJB will reuse BMT started transaction
   @EJB
   AnotherBean reuseTx;

   public void testMethod() throws Exception {
      // Begin Tx and check it's status - compare value with:
      // http://java.sun.com/javaee/6/docs/api/constant-values.html#javax.transaction.Status.STATUS_ACTIVE
      utx.begin();
      System.out.println("####testMethod#### Tx status: " + utx.getStatus());

      // Our BMT started a Tx - now invoke CMT and reuse this Tx
      // Notice: AnotherBean has MANDATORY Tx attribute, so if no Tx would
      // exist, the AnotherBean couldn't be even invoked.
      reuseTx.testIt();

      // Check if the CMT AnotherBean affected Tx we started. 
      System.out.println("####testMethod#### Tx status: " + utx.getStatus());

      // Just to prevent exceptions.
      utx.rollback();
   }

   // Implicitly CMT - must reuse the Tx
   @Stateless
   @TransactionAttribute(TransactionAttributeType.MANDATORY)
   public static class AnotherBean {

      // It's CMT, so Tx control is made through it's context.
      @Resource
      SessionContext sctx;

      // Can inject it, but cannot use it - will throw an Exception.
      @Resource
      UserTransaction utx;

      public void testIt() throws Exception {

         // Give a sign that rollback must be made.
         sctx.setRollbackOnly();
         System.out.println("####testIt#### Tx status: " + getTxStatus());
      }
   }

   // Small hack to get the status of current thread JTA Tx
   // http://java.sun.com/javaee/6/docs/api/javax/transaction/TransactionSynchronizationRegistry.html
   private static int getTxStatus() throws Exception {
      InitialContext ctx = new InitialContext();
      TransactionSynchronizationRegistry tsr = (TransactionSynchronizationRegistry)
                               ctx.lookup("java:comp/TransactionSynchronizationRegistry");

      return tsr.getTransactionStatus();
   }
}

Этот EJB можно вызвать, например, из Singleton EJB с помощью @Startup, чтобы сразу увидеть, как отреагирует ваша AS.

На Glassfish 3.1.1 вы получите следующий результат:

ИНФОРМАЦИЯ: ####testMethod#### Статус передачи: 0

ИНФОРМАЦИЯ: #### testIt #### Статус передачи: 1

ИНФОРМАЦИЯ: ####testMethod#### Статус передачи: 1

Ура!

Вот как CMT управляет транзакциями. Контейнер автоматически фиксирует транзакцию, когда возвращается бизнес-метод. Если вы этого не сделаете, используйте BMT вместо CMT.

Другие вопросы по тегам