Timeouty JTA w świecie Javy EE
System może działać bez zarzutu przez lata, pewnego dnia jednak, zaczynają się pojawiać timeouty JTA (Java Transaction API):
[WebLogic] Transaction Rolledback.: weblogic.transaction.internal.TimedOutException: Transaction timed out after 301 seconds
(...) No further JDBC access is allowed within this transaction....
[WebSphere] TimeoutManage I WTRN0006W: Transaction (...) has timed out after 120 seconds.
[JBoss/WildFly] javax.transaction.RollbackException: ARJUNA016102: The transaction is not active!
Chociaż jest to specyficzne dla niektórych przypadków wykorzystania, występowanie timeoutów JTA nie jest tak rzadkie i może się zdarzyć na produkcji. Szczególnie gdy ilość danych do przetworzenia drastycznie wzrośnie względem limitów określonych na samym początku budowania systmu. Na podobny problem często można natknąć się, zmieniając serwer aplikacyjny.
Zażegnanie tego problemu w Springu to zazwyczaj bułka z masłem. Na poziomie klasy/interfejsu/metody możemy to skonfigurować za pomocą adnotacji @org.springframework.transaction.annotation.Transactional(timeout = SECONDS)
. Interfejs jest ten sam, niezależnie od tego, czy chcemy użyć JTA dostarczonego przez kontener, osadzonego, czy menadżera transakcji dla pojedynczego źródła danych (JDBC).
W świecie Javy EE nie jest to jednak takie proste. Odpowiednik javax.transaction.Transactional
w obecnym stanie nie umożliwia ustawienia limitu czasu transakcji, taka specyfikacja nie została jeszcze przygotowana. W związku z tym domyślny limit czasu transakcji, jak i możliwość skonfigurowania go na poziomie klasy/metody pozostaje w rękach implementatorów kontenerów.
Przykład i wartości domyślne
Zobaczmy, jak to działa w środowisku JavaEE. Zdefiniujemy najprostszy Singleton Bean, który zostanie utworzony podczas uruchamiania aplikacji:
package mypackage;
import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;
@Startup
@Singleton(name = "StartupBean")
public class StartupBean {
@PostConstruct
public void init() {
System.out.println("Starting the initialization");
try {
Thread.sleep(31_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Finished initializing");
}
}
Pod względem czytelności, w tym beanie brakuje wiele mówiącej adnotacji @TransactionManagement(TransactionManagementType.CONTAINER)
. Kwestia ta może nie być zbyt intuicyjna dla osób zaczynających podróż z Javą biznesową, ale:
Jeśli ta adnotacja nie jest używana, zakłada się, że transakcjami komponentu zarządza kontener.
Ponadto na poziomie metody brakuje również adnotacji @TransactionAttribute(TransactionAttributeType.REQUIRED)
:
Jeśli nie dodano adnotacji TransactionAttribute, a komponent bean używa demarkacji transakcji zarządzanej przez kontener, przyjmowana jest semantyka atrybutu transakcji REQUIRED.
Podczas tworzenia nowych beanów należy pamiętać o tym sposobie działania. Ostatecznie, próbując wdrożyć tego beana na serwerze aplikacyjnym WebLogic 12.2, otrzymuję:
weblogic.ejb.container.InternalException: Transaction marked rollback or not expected transaction status: 4
i WebLogic nie jest w stanie wdrożyć aplikacji (pozostaje w stanie STATE_NEW). Dzieje się tak, ponieważ metoda @PostConstruct
jest również objęta transakcją JTA, która w tym przypadku ma domyślny limit czasu 30 sekund.
Aby wyłączyć JTA, możemy użyć adnotacji @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
na poziomie metody/klasy, bądź adnotacji @TransactionManagement(TransactionManagementType.BEAN)
na poziomie klasy.
Timeouty
Z drugiej strony, jeśli naprawdę mamy logikę transakcyjną w podobnej metodzie (prawdopodobnie już nie w @PostConstruct
), będziemy zainteresowani konfiguracją limitu czasu transakcji, na podobnej zasadzie co w Springu. W takim przypadku musimy zapoznać się z dokumentacją konkretnej implementacji kontenera JEE.
W przypadku WebLogica limit czasu JTA skonfigurować możemy na poziomie domeny poprzez konsolę WebLogic.
Spowoduje to dodanie kilku wpisów do pliku config.xml
znajdującego się w folderze config
w miejscu instalacji domeny:
<!--...-->
</security-configuration>
<jta>
<timeout-seconds>31</timeout-seconds>
<abandon-timeout-seconds>86400</abandon-timeout-seconds>
<forget-heuristics>true</forget-heuristics>
<before-completion-iteration-limit>10</before-completion-iteration-limit>
<max-transactions>10000</max-transactions>
<max-unique-name-statistics>1000</max-unique-name-statistics>
<checkpoint-interval-seconds>300</checkpoint-interval-seconds>
<parallel-xa-enabled>true</parallel-xa-enabled>
<unregister-resource-grace-period>30</unregister-resource-grace-period>
<two-phase-enabled>true</two-phase-enabled>
<clusterwide-recovery-enabled>false</clusterwide-recovery-enabled>
<tightly-coupled-transactions-enabled>false</tightly-coupled-transactions-enabled>
<tlog-write-when-determiner-exists-enabled>false</tlog-write-when-determiner-exists-enabled>
</jta>
<!--...-->
Bardziej szczegółowej konfiguracji (na poziomie beana) można dokonać za pomocą deskryptora weblogic-ejb-jar.xml
umieszczonego w katalogu WEB-INF archiwum WAR lub w katalogu META-INF archiwum JAR. Konfiguracja jest dopasowywana poprzez wartość elementu ejb-name
zdefiniowaną również przez wartość elementu name
adnotacji beana.
<?xml version = '1.0' encoding = 'UTF-8'?>
<weblogic-ejb-jar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-ejb-jar http://www.bea.com/ns/weblogic/weblogic-ejb-jar/1.0/weblogic-ejb-jar.xsd"
xmlns="http://www.bea.com/ns/weblogic/weblogic-ejb-jar">
<weblogic-enterprise-bean>
<ejb-name>StartupBean</ejb-name>
<transaction-descriptor>
<trans-timeout-seconds>60</trans-timeout-seconds>
</transaction-descriptor>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>
W przypadku dostawców innych kontenerów sytuacja może się nieco różnić.
Przykładowo przewodnik instalacji Oracle Commerce Platform
daje nam ogólny pogląd na JBossa, WebLogica i WebSphere.
JBoss/WildFly ma dodatkową adnotację @org.jboss.ejb3.annotation.TransactionTimeout
na podobieństwo do Springa.
Zbieżnie do WebLogica, JBoss/Wildfly zapewnia analogiczny element konfiguracyjny <tx:trans-timeout>
w deskryptorze jboss-ejb3.xml
.
Z kolei w kontenerze WebSphere element ten nosi nazwę <global-transaction>
i jest umieszczony w deskryptorze ibm-ejb-jar-ext.xml
.
Ostatecznie, aby uzyskać większą kontrolę, możemy wykorzystać BMT (Bean Managed Transaction). W takim przypadku użylibyśmy javax.transaction.TransactionManager.setTransactionTimeout(int seconds)
lub javax.transaction.UserTransaction.setTransactionTimeout(int seconds)
w celu ustawienia limitu czasu konkretnych transakcji.
Podsumowanie
Możemy wyciągnąć kilka kluczowych wniosków:
- API
@Transactional
różni się między Javą EE i Springiem; - beany EJB mają domyślnie włączone CMT (Container Managed Transactions), chyba że jawnie zadeklarujemy to inaczej;
- kontrolowanie limitu czasu CMT jest specyficzne dla implementacji, najlepiej sprawdzić dokumentację kontenera.