Dlaczego CMT commit przy wyjściu metody EJB, gdy atrybut transakcji jest "wymagany"?

Konsekwentnie stwierdzam, że moja już istniejąca transakcja zostaje popełniona w dowolnej metodzie EJB oznaczonej @ejb.transaction type="Required". Czy to może być poprawne?

Oczekuję, że EJB "wymagający" transakcji oznacza: jeśli już istnieje, grzecznie pozostawi ją niezatwierdzoną, gdy zostanie wykonana, aby ktokolwiek wywołał begin () mógł nadal używać jej do dalszych operacji przed wywołaniem commit() lub rollback(). [Oczywiście, jeśli nie było transakcji w pierwszej kolejności, to EJB metoda wywoła zarówno begin(), jak i commit()/rollback().]

Czy moje oczekiwania są błędne, czy powinienem szukać błędu w konfiguracji?

Warto dodać, że używam Hibernate 3 wewnątrz EJB. Otrzymuję UserTransaction przed wywołaniem metody EJB. Wrapper generowany przez EJB wywołuje ServerTransaction.commit() przy wyjściu, który hibernuje i wykorzystuje możliwość zamknięcia sesji. Błąd, który dostaję, to wyjątek leniwego ładowania Hibernate, ponieważ sesja jest zamknięta kiedy próbuję uzyskać dostęp do getterów na obiekcie utrzymywanym w hibernacji. Więc technicznie, nie jestem w 100% pewien, czy ServerTransaction.commit() zaobserwowałem koniecznie popełnił UserTransaction zacząłem (może ServerTransaction.commit() nie zawsze wynika z "prawdziwego" commit?), ale jeśli nie-to na jakiej podstawie Hibernate zamyka sesję?

Aktualizacja: wydaje mi się, że moje powyższe założenia były poprawne, ale moje obserwacje były trochę dziwne. Poniżej znajduje się moja własna odpowiedź.

Author: Tiny, 2011-08-25

4 answers

Wymagane może być złe

Osobiście nie podoba mi się wymagany atrybut transakcji i zdecydowanie odradzam jego użycie.

Leniwe tworzenie transakcji (czyli to, co wymagane) powoduje, że tak naprawdę nie wiadomo, kiedy i gdzie transakcja została faktycznie rozpoczęta i kiedy zostanie zatwierdzona. To jest Nie dobra rzecz. Ludzie powinni wyraźnie zaprojektować granice transakcji.

Obowiązkowe i UserTransaction są bratnimi duszami

Twoje pragnienie użycie UserTransaction jest bardzo dobre i Działa z CMT-nie ma różnicy między transakcją JTA rozpoczętą przez UserTransaction dostarczoną przez kontener, a transakcją JTA rozpoczętą dla ciebie przez kontener.

Podejście, które zalecam, to zmiana wszystkich wymaganych zastosowań na obowiązkowe . Z obowiązkowym kontenerem Nie rozpocznie transakcje za Ciebie. Zamiast tego będzie chronić fasolę, zapewniając, że nie można jej wywołać, chyba że transakcja jest w postęp. Jest to niesamowita i niewykorzystana funkcja. Obowiązkowe jest najlepszym przyjacielem każdego, kto chce stworzyć prawdziwie deterministyczną aplikację transakcyjną i ją egzekwować. Dzięki tej konfiguracji możesz zakochać się w CMT.

W tym scenariuszu rozpoczynasz transakcje od UserTransactions i wtedy kontener jest jak twój wielki ochroniarz wyrzucający ludzi na krawężnik, chyba że odpowiednio rozpoczęli transakcję przed próbą wywołania Twojej kod.

Rozważania

  • Servlet lub EJB BMT mogą używać UserTransaction.
  • CMT beans nie może używać UserTransaction, ale może uczestniczyć w transakcji rozpoczętej przez UserTransaction (jak wspomniano powyżej, transakcja JTA jest transakcją JTA).
  • BMT beans nie może uczestniczyć w istniejącej transakcji. Jeśli wywołasz BMT bean, gdy transakcja jest już w toku, transakcja zostanie zawieszona przez kontener przed wywołaniem BMT fasoli i wznowione po zakończeniu metody.
  • @Resource UserTransaction otrzymasz transakcję użytkownika poprzez wstrzyknięcie
  • java:comp/UserTransaction otrzymasz transakcję użytkownika poprzez lookup
  • @TransactionAttribute(MANDATORY) użyte na poziomie klasy będą miały wpływ na metody tej klasy (tj. metody fooClass.getDecaredMethods()). Metody superklas i podklas będą domyślnie ustawione na @TransactionAttribute(REQUIRED), chyba że klasy te są również wyraźnie oznaczone @TransactionAttribute(MANDATORY)
  • jeśli masz dość wywoływania userTransaction.begin() i userTransaction.commit() i wykonywania odpowiedniego wyjątku obsługa, rozważ @TransactionAttribute(REQUIRES_NEW). Granice transakcji będą nadal udokumentowane i wyraźne.
  • RuntimeExceptionS wyrzucony z metody CMT bean spowoduje, że transakcja zostanie oznaczona jako rollback, nawet jeśli złapiesz i obsłużysz wyjątek w kodzie wywołującym. Użyj @ApplicationException, aby wyłączyć to indywidualnie dla niestandardowych klas WYJĄTKÓW środowiska wykonawczego.

Utrata kontekstu transakcji

Kilka rzeczy może spowodować zatrzymanie, zawieszenie lub w przeciwnym razie nie rozmnażają się do zwanej fasoli.

  • fasola BMT zatrzymuje propagację transakcji. Jeśli transakcja w toku wywoła BMT bean, ta transakcja zostanie zawieszona przed wywołaniem BMT bean i zostanie wznowiona po powrocie bean. Fasola BMT może być źródłem transakcji, ale nie może uczestniczyć w istniejących transakcjach. Jeśli propagacja w tajemniczy sposób zawodzi, upewnij się, że nie ma niezamierzonych wywołań do fasoli BMT w połowie transakcji.
  • nie stosować Wszystko inne niż dostarczone przez kontener UserTransaction lub dostarczone przez kontener metody, takie jak SessionContext.setRollbackOnly do zarządzania transakcjami. Korzystanie z "specyficznego dla Menedżera zasobów interfejsu API rozgraniczania transakcji", takiego jak JPA EntityTransaction lub java.sql.Connection.commit() obejdzie zarządzanie transakcjami.
  • nie zakładaj własnych wątków. Propagacja transakcji odbywa się na podstawie wątku. Nie ma standardowych API w Java EE, które obsługują transakcje obejmujące wiele wątków. Jeśli opuścisz wątek od założenia własnego wątku lub za pomocą @Asynchronous, pozostawisz istniejącą transakcję za sobą.
 20
Author: David Blevins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-30 20:54:19

Bliższa inspekcja ujawnia inną odpowiedź niż sugerowana powyżej. To, co widzę, to fakt, że rozpoczęta przeze mnie UserTransaction pozostała otwarta, ale CMT stworzyło nową transakcję przy wejściu do metody EJB, pomimo atrybutu "Required".

Wierzę, że to się dzieje, ponieważ złamałem Zasady. :) Nie powinieneś mieć dostępu do API UserTransaction podczas korzystania z CMT. CMT szczęśliwie zignorował mój UserTransaction i zaczął swój własny, zajmując jego miejsce jako arbiter wszystkich granice transakcji. Od momentu rozpoczęcia transakcji, również ją zobowiązała i oczywiście pozostawiła moją UserTransaction nietkniętą.

Wydaje mi się kruche i głupie, być może naiwne zdanie, ale wydaje się zgodne z "zasadami", gdy je czytam. Nie wiem dlaczego CMT nie chce grać ładnie z Usertransakcje rozpoczęte na wyższym poziomie. Być może, aby zmusić deweloperów do "zrobienia właściwej rzeczy J2EE" i stworzyć kolejną warstwę fasoli sesji, aby obsłużyć szerszy kontekst transakcji. To by praca, ponieważ CMT zarządzałby zewnętrzną transakcją i dlatego byłoby w porządku z zaciąganiem żadnych wewnętrznych transakcji, i wierzę, że w takim przypadku transakcja "parasolowa" nie byłaby popełniona przez wewnętrzny EJB; CMT poczekałby, aż zewnętrzna transakcja się zakończy, a następnie zobowiązałby się do całej sprawy. Właściwie to musiałoby.

Nie jestem w nastroju, aby tworzyć więcej EJB sesji w tej już EJB-nadęty aplikacji, ale może to być jedyne rozwiązanie brakuje zgrywania CMT w całości kilka miejsc, których wolałbym nie dotykać.

 1
Author: nclark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-26 15:08:58

NClark, rozważ następujący kod, który uruchomiłem na GlassFish 3.1.1. Mam nadzieję, że w jakikolwiek sposób pomoże: -)

@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();
   }
}

Ten EJB można wywołać np. z Singleton EJB z @Startup, aby natychmiast zobaczyć, jak zareaguje Twój AS.

Na Glassfish 3.1.1 otrzymasz następujący wynik:

INFO: # # # # testMethod # # # # TX status: 0

INFO:#### # # # TX status: 1

INFO: # # # # testMethod # # # # TX status: 1

Zdrówko!
 1
Author: Piotr Nowicki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-30 23:14:28

Tak działają transakcje zarządzane przez CMT. Kontener automatycznie zatwierdzi transakcję po powrocie metody biznesowej. Jeśli tego nie zrobisz, użyj BMT zamiast CMT.

 0
Author: Kris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-26 12:23:32