|
1 Co zawiera tekst ? |
W poniższym tekście zostanie omówiony sposób używania przez programistę transakcji w środowisku J2EE. Zaprezenowane zostaną różne rodzaje przetwarzania transakcji dostępne na platformie J2EE - zarządzanie transakcjami w sposób automatyczny, na poziomie kontenera EJB, dokonywane przez serwer aplikacyjny oraz zarządzanie "ręczne" na poziomie ziaren EJB lub ich metod, poprzez Java Transaction API. Zajmiemy się też, choć skrótowo, bo jest on nieistotny dla przeciętnego programisty, interfejsem Java Transaction Services, który jest w pewnym uproszczeniu niskopoziomową biblioteką Javową do obsługi transakcji. Do zrozumienia poruszanych problemów niezbędna okaże się wiedza z przedmiotu "Programowanie Współbieżne", a konkretnie jej część dotycząca transakcji oraz ogólna znajomość środowiska J2EE.
2 Transakcje |
2.1 Czym jest transakcja ? |
2.2 Właściwości transakcji |
atomowe - co oznacza że albo wykonują się wszystkie operacje z transakcji albo żadna z nich.
zgodne - to znaczy po ich wykonaniu system nie może się rozpójnić
izolowane - wykonanie jednoczesne kilku transakcji daje takie same efekty jak wykonanie ich po kolei
trwałe - efekty wykonia transakcji są trwałe.
3 Transakcje w środowisku J2EE |
Transakcje w J2EE składają się z wywołania metody lub kilku metod ziaren EJB i operacji na EIS (Enterprise Information System - baza danych). W obecnej implementacji SUNowskiego mechanizmu zarządzającego transakcjami są one "płaskie", natomiast transakcje zagnieżdżone są niezaimplementowane. Oznacza to, że w każdej chwili wątek może uczestniczyć w maksimum jednej transakcji. Specyfikacja J2EE wymaga obsługi transakcji płaskich, a nie wymaga ( ale też nie zabrania ) obsługi wersji zagnieżdżonej. Próba wywołania kolejnej transakcji, gdy jakaś już się odbywa, a implementacja JTA nie obsługuje transakcji zagnieżdżonych spowoduje podniesienie wyjątku.
Transakcje w J2EE są rozproszone - mogą dotyczyć kilku baz danych ( EIS ) i może w nich uczestniczyć wiele programów klienckich i ziaren EJB jednocześnie. Programista ma zapewnioną pełną przezroczystość lokalizacji bytów uczestniczących w transakcji, a to dzięki protokołowi 2PC
3.1 Protokół zatwierdzania dwufazowego 2PC ( two Phase Commit ) |
W J2EE zastosowano model dwufazowego zatwierdzania transakcji. W pierwszej fazie główny koordynator transakcji wysyła do wszystkich bytów uczestniczących w transakcji ( transakcje wykonują się w środowisku rozproszonym ) prośbę o zgodę na commit. Jeśli żaden z nich nie zaprotestuje to następuje zatwierdzenie transakcji i wszyscy są szczęśliwi. Jeśli natomiast któryś z koordynatorów nie odpowie na prośbę o zatwierdzenie wysłaną w pierwszej fazie lub odpowie negatywnie to wtedy transakcja jest anulowana a koordynator transakcji informuje wszystkie byty w niej uczestniczące o niepowodzeniu. Dzięki protokołowi 2PC możliwa jest koordynacja rozproszonych transakcji, np. w sytuacji gdy w jednej transakcji zmieniamy dane ulokowane w 2 różnych bazach danych ( 2 różnych EIS).
4 Transakcje zarządzane przez kontener ( Container-managed ) |
Sterowanie transakcjami zarządzanymi przez kontener odbywa się poprzez ustawienie atrybutu trans-attribute w deskryptorze aplikacji. Kontener sam korzysta z JTA, zwalniając programistę z konieczności pisania kodu obsługującego transakcję.
Jest to więc sterowanie deklaratywne, nie wymagające od programisty samodzielnej obsługi transakcji. Atrybut musi być ustawiony dla każdej metody interfejsu zdalnego w ziarnach sesyjnych i każdej metody interfejsu zdalnego i domowego w ziarnie encyjnym dla której chcemy zdefiniować sposób obsługi transakcyjności.
4.1 Atrybuty transakcji zarządzanych przez kontener |
Required - Kontener upewnia się, że metoda EJB zawsze będzie wołana w transakcji JTA. Jeśli klient nie jest aktulanie związany z żadną transakcją to zostanie utworzona nowa transakcja, która zostanie zakończona wraz z zakończeniem wykonania metody. To najczęsciej używany atrybut.
RequiresNew - Kontener zawsze tworzy nową transakcję przed wywołaniem metody ziarna EJB. Jeśli klient wykonuje inną transakcję to jest ona zawieszana. Przydatne gdy chcemy by efekty wykonania metody nie cofnęły się, np. przy logowaniu.
NotSupported - Jeśli klient wykonuje transakcję to jest ona zawieszana na czas wołania metody ziarna EJB. Jeśli nie wykonuje to nic się nie dzieje. Przydatne jeśli wykonawca transakcji nie jest obsługiwany przez J2EE JTA i J2EE nie ma kontroli nad jego transkacją.
Supports - Jeśli klient wykonuje akurat transakcję to wołanie metody ziarna EJB odbędzie się w jej kontekście, a jeśli nie to odbędzie się ono bez transakcji. Użycie tej metody jest ryzykowne, bo nie wiemy co się stanie.
Mandatory - Klient musi wywołać metodę ziarna w swojej transakcji, bo jeśli tak nie jest podnosi się wyjątek javax.transaction.TransactionRequiredException. Przydatne do testowania czy jesteśmy w transakcji.
Never - Metoda z ziarna EJB wykonuje się bez transakcji, a jeśli klient wykonuje transakcję to podnoszony jest wyjątek java.rmi.RemoteException. Przydatne do testowania czy jesteśmy w transakcji.
Najlepiej tłumaczy to rysunek. Ziarno 1 wykonuje metodę A w której woła metodę B ziarna 2:
Atrybut transakcji | Transakcja klienta | Metoda ziarana EJB |
---|---|---|
Required |
Brak | T2 |
T1 | T1 | |
RequiresNew |
Brak | T2 |
T1 | T2 | |
Mandatory |
Brak | Błąd |
T1 | T1 | |
NotSupported |
Brak | Brak |
T1 | Brak | |
Supports |
Brak | Brak |
T1 | T1 | |
Never |
Brak | Brak |
T1 | Błąd |
Budowa pliku XML z deskryptorem aplikacji:
<enterprise-beans>
<session>
...
<transaction-type>Container</transaction-type>
...
</sesion>
</enterprise-beans>
...
<container-transaction>
<method>
//definicja metody
</method>
<trans-attribute>Required | ReqiresNew | ... </trans-attribute>
</container-transaction>
4.2 Pozostałe cechy obsługi przez kontener |
Domyślnym atrybutem transakcji obsługiwanych przez kontener jest Reqired
. Jego obsługa wymusza na
platformie J2EE duże narzuty czasowe. W związku z tym, kiedykolwiek programista jest pewien, że poziom ochrony
gwarantowany przez Reqired
jest za silny warto obniżyć go do innego, zmieniając atrybuty transakcji.
Zarządzanie przez kontener umożliwia także wycofywanie transakcji. Transakcje obsługiwane przez kontener możemy przerywać poprzez:
setRollbackOnly();
z interfejsu EJBContext
EJBException
)transaction.timeout
).
W Sunowskiej implementacji JTA za domyślny przyjęto timeout zerowy, oznaczający że czas czekania na powodzenie
transakcji (od jej rozpoczęcia) jest nieskończony.
Kontener rozpoczyna transakcję tuż przed wywołaniem metody ziarna EJB, a kończy tuż przed jej zakończeniem.
4.3 Uwagi |
Jeśli wycofywana jest transakcja zmieniająca zmienne instancyjne ziarna sesyjnego to ich stan nie jest
przywracany. Aby mimo wszysko osiągnąćten efekt należy zaimplementować inetrfejs SessionSynchronization
,
a w szczególności jego metody afterBegin(
) i afterCompletion()
.
Są one wołane odpowiednio przed i po rozpoczęciu transakcji i programista może zadbać w nich o przywrócenie
odpowiednich wartości zmiennym instancyjnym
W transakcjach zarządzanej przez kontener nie wolno używać metod commit
,
setAutoCommit
, oraz rollback
z interfejsu java.sql.Connection
,
metody getUserTransaction
z interfejsu javax.ejb.EJBContext
oraz
metod z interfejsu javax.transaction.UserTransaction
.
Sun zaleca stosowanie tam gdzie tylko można automatycznej obsługi transakcji przez kontener.
4.4 Zalety i wady automatycznego zarządzania transakcjami |
Zalety:
Wady:
5 Transakcje zarządzane na poziomie ziarna ( Bean-managed ) - JTA |
5.1 Wersja |
Najświeższą dostępną wersją specyfikacji JTA jest wersja 1.0.1 pochodząca z kwietnia 1999.
5.2 Jak używać JTA ? |
#import javax.transaction.* public void pobierzKase(double ile) { UserTransaction ut = ejbContext.getUserTransaction(); try { ut.begin(); stanRachunku -= ile; wyplacGotowke(ile); ut.commit(); } catch (Exception ex) { try { ut.rollback(); } catch (SystemException syex) { throw new EJBException ("Rollback failed: " + syex.getMessage()); } throw new EJBException ("Transaction failed: " + ex.getMessage()); } }
Ziarno EJB najpierw pobiera obiekt reprezentujący transakcyjność, ut
. Następnie rozpoczynana
jest transakcja ( metoda begin()
). Po operacjach bankomatowych następuje zatwierdzenie transakcji
( metoda commit
). Jeśli w którymkolwiek momencie coś pójdzie nie tak zostanie wygenerowany
wyjątek, który przechwycimy i obsłużymy wykonując rollback
.
5.3 Interfejs UserTransaction |
Aby skorzystać z transakcji JTA w aplikacji klienckiej lub zieranie EJB należy pobrać referencję do
obiektu interfejsu UserTransaction
z kontekstu w którym się znajdujemy ( czasem trzeba w tym celu
użyć JNDI )
Później można już korzystać z funkcji oferowanych przez JTA. Są to:
begin()
- metoda rozpoczynająca transakcjęcommit()
- metoda kończąca tarnsakcjęrollback()
- metoda wycofująca transakcję. Interesujące jest to, że stanowe ziarna sesyjne nie muszą kończyć
transakcji wraz z zamknięciem połączenia z bazą danych natomiast bezstanowe - muszą.getStatus()
- zwraca status wykonywanej przez wątek transakcji
(STATUS_ACTIVE, STATUS_COMMITTED, STATUS_COMMITTING, STATUS_MARKED_ROLLBACK, STATUS_NO_TRANSACTION,
STATUS_PREPARED, STATUS_PREPARING, STATUS_ROLLEDBACK, STATUS_ROLLING_BACK, STATUS_UNKNOWN
)setTransactionTimeout(int seconds)
- wolno wołać jedynie przed begin()
.
Ustawia timeout w transakcjach.5.4 Inne interfejsy |
Oprócz interfejsu UserTransaction
JTA udostępnia także 2 inne interfejsy. Są to: interfejs
TransactionManager
umożliwiający serwerowi aplikacji kontrolę transakcji
( programista - łapki precz ) oraz Transaction
którego obiekty reprezentują transakcje.
W skład Java Transaction API wchodzi ponadto pakiet javax.transaction.xa
udostępniający funkcje
standardu XA opartego na specyfikacji X/Open CAE.
5.5 Poziomy Izolacji |
ReadCommitted Transakcje mogą czytać jedynie dane zatwierdzone.
RepeatableRead jw, a ponadto wielokrotne czytanie tych samych danych przez transakcje na pewno zwróci te same dane.
Serializable - jw. a ponadto wielokrotne wykonanie zapytania w sytuacji gdy czytane dane się zmieniły daje te same wyniki.
Poziomu izolacji tranasakcji nie wolno zmieniać w czasie transakcji. Przykład zmiany poziomu izolacji transakcji:
Connection con = ... ; con.setTransactionIsolation(TRANSACTION_READ_COMMITTED);
5.6 Uwagi |
W transakcjach JTA zabronione jest używanie metod:
getRollbackOnly()
, setRollbackOnly
z interfejsu EJBContext
(tych metod wolno używać jedynie przy sterowaniu transakcjami przez kontener ).
W transakcjach zarządzanych przez ziarno należy zamiast tego posłużyć się metodami
getStatus
i rollback
z interfejsu UserTransaction
.
5.7 Zalety i wady ręcznego zarządzania transakcjami |
Zalety:
Wady:
6 Testy porównawcze |
Na Akademii Górniczo Hutniczej przeprowadzono testy sprawdzające szybkość różnych rodzajów transakcji. Testowano szybkość wykonania 100 operacji przelewu bankowego, z których każda składała się z pobrania pieniędzy z jednego konta i przelania ich na inne konto:
public void transferToSaving(double amount) throws InsufficientBalanceException { checkingBalance -= amount; savingBalance += amount; try { updateChecking(checkingBalance); if (checkingBalance < 0.00) { context.setRollbackOnly(); throw new InsufficientBalanceException(); } updateSaving(savingBalance); } catch (SQLException ex) { throw new EJBException ("Transaction failed due to SQLException: " + ex.getMessage()); } }Wyniki tych testów były następujące:
1. Container-M. Tr.; |
2. Bean-M. Tr. JDBC; |
3. Bean-M. Tr. JTA | |
Elapsed time for 100 methods _transferToSaving_ |
20570[ms] |
19540[ms] |
19700[ms] |
Przeprowadzono także testy porównawcze dwóch aplikacji, mające na celu zbadanie narzutu czasowego spowodowanego
ustawieniem metody transferToSaving
jako transakcyjnej i nietransakcyjnej.
Obie aplikacje zawierały 'Session Bean' i transakcje były zarządzane przez kontener. Testy zakończyły się
następującymi wynikami:
metoda transakcyjna; | metoda nietransakcyjna | |
Elapsed time for 100 methods _transferToSaving_: |
20570[ms] |
12940[ms] |
Testy jasno pokazują, że użycie JTA bądź JDBC zamiast zarządzania transakcjami przez kontener jest niewarte poniesionego wysiłku i ryzyka popełnienia błędu oraz że tam gdzie można tego uniknąć lepiej nie stosować transakcyjności.
7 Java Transaction Service |
7.1 Wersja |
Aktualnie dostępną wersją specyfikacji JTS jest wersja 1.1 z listpada 1997.
7.2 Czym jest JTS ? |
JTS jest Sunowską implementacją zarządcy rozproszonych transakcji. Czuwa on nad odpowiednim ich przebiegiem a jego metody są udostępniane poprzez Java Transaction API. Jest więc on niskopoziomową biblioteką do obsługi transakcji. Programista Javy nie ma potrzeby korzystania z JTS.
7.3 Jak działa JTS ? |
Java Transaction Service ( JTS ) pełni rolę koordynatora transakcji dla wszystkich komponentów architektury EJB. W terminologii JTS koordynator ten nazywany jest transaction managerem. Byty chronione przez transakcje takie jak bazy danych są natomiast nazywane resource managers. Kiedy aplikacja rozpoczyna transakcję tworzy ona obiekt transakcji, reprezentujący transakcję. Następnie poszczególne resource managers są wywoływane aby wykonać ciało transakcji. Transaction manager nadzoruje pracę wszyskich resorce managers biorących udział w transakcji.
Pierwsze wołanie każdego resource managera przez aplikację identyfikuje obecną transakcję. Np. jeśli aplikacja używa relacyjnej bazy danych to gdy woła interfejs JDBC kojarzy obiekt transakcji z łączem JDBC. Później już wszystkie wołania wykorzystujące to łącze są wykonywane w imieniu transakcji bazodanowej - dopóki nie zakończy się ona.
Zazwyczaj aplikacja kończy transakcje wołaniem metody xa_commit()
i transakcja jest zatwierdzana.
Jeśli aplikacja z jakiegoś powodu nie może zakończyć operacji umieszczonych wewnątrz ciała transakcji to
wycofuje się ona wołając xa_rollback()
. Jeśli aplikacja odnosi porażkę JTS anuluje ją.
Natomiast jeśli aplikacji uda się dokonać tarnsakcji to wywołuje ona JTS aby zatwierdzić transakcję.
JTS wykonuje protokół zatwierdzania dwufazowego, upewniając się że zmiana została rozpropagowana i zaakceptowana
przez wszystkie resurce menagery uczestniczące w transakcji.
7.4 Cechy JTS |
JTS umożliwia:
Cechy JTS to:
8 Łącza do innych stron |