Почему CMT фиксируется при выходе из метода EJB, когда атрибут транзакции «Обязательный»? - PullRequest
8 голосов
/ 25 августа 2011

Я постоянно обнаруживаю, что моя уже существующая транзакция фиксируется внутри любого метода EJB-компонента, помеченного @ejb.transaction type="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 ]

20 голосов
/ 28 августа 2011

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

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

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

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

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

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

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

Вопросы

  • Сервлет или BMT EJB могут использовать транзакцию пользователя.
  • Бины 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, вы оставите свою существующую транзакцию позади.
1 голос
/ 31 августа 2011

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

ура!

1 голос
/ 26 августа 2011

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

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

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

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

0 голосов
/ 26 августа 2011

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

...