Spring - Transakcie v Jave
V predchádzajúcom lekcii, Java spustenie - Programové argumenty , sme si predviedli spustenie s programovými argumenty.
Transakcia je postupnosť databázových príkazov, ktoré sa vykonajú v jednom behu. Ak sa nejaký z nich nevykoná (napr. Neprejde cez integritné obmedzenia), nevykoná sa žiadny z príkazov (všetko alebo nič).
Základné pojmy
- BEGIN - začne transakciu
- COMMIT - všetky príkazy sa vykonajú postupne
- ROOLBACK - nastala chyba v nejakom príkazu. Dáta sa vráti do pôvodného stavu (pred začiatkom transakcie).
ACID
Pravidlá, čo musí každá transakcia spĺňať.
- Atomicita (Atomicita) - transakcia prebehne buď celá alebo neprebehne vôbec
- Consistency (konzistencia) - pri zapísanie (vyhotovenie príkazov) nie je porušené žiadne integritné obmedzenie
- Isolation (izolovanosť) - transakcia je čierna skrinka, operácia sa navzájom v rámci viac transakcií neovplyvní
- Durability (trvalosť) - raz uložené zmeny nezmizne a sú uložené natrvalo
Spring
Transaction Manager
Riadi prácu s transakciami. V praxi sa s ním stretnete len výnimočne. Ide skôr o pochopenie princípu fungovania.
public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
Najskôr si hovoríte, kde je metóda na začatie transakcie (begin). Metóda getTransaction je veľmi špecifická. Ak transakcia existuje vráti ju, ak nie vytvorí ju. Transakciu tu reprezentuje trieda TransactionStatus.
Nastavenia vo Spring
Je potrebné zaviesť transakčné manager do aplikačného kontextu:
<tx:annotation-driven transaction-manager="transactionManager"/> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
<tx: annotation-driven... nám zaistí, že Spring bude čítať anotácie (@Transactional). Spring zariadi všetku špinavú prácu za nás (vytvorenie proxy tried ...).
Najskôr si všimnete, že je potreba transactionManager odovzdať datasource. DataSource slúži pre pripojenie aplikácie k databáze. Sedláčka povedané v ňom nadefinujete adresu, meno a heslo a on už sa postará o pripojení. Viac sa môžete dočítať v dokumentácii k datasource
Bez anotáciu
Ak poznáte princíp fungovania, neprekvapí vás nečakané situácie. Pri použití nástroja na integritné testovanie sme zistili, že mechanizmus pomocou anotácií nepracuje podľa očakávania. Vytvorili sme teda abstraktné triedu, ktorá mala na starosti riadenie transakcií.
Integračné test
Testuje integráciu (funkčnosť prepojenie) viac komponentov dohromady.
Príklad: "Mám dáta v nejakom stave. Vykonám postupnosť príkazu naprieč aplikácií. Dáta sa zmenili podľa očakávania.
public abstract class AbstractTransactionTest { @Autowired private DataSourceTransactionManager manager; private TransactionStatus transactionStatus; @Before public final void beforeTest() { //typ transakce int propagationBehavior = DefaultTransactionDefinition.PROPAGATION_REQUIRED; // vytvori inicialni stav transakce DefaultTransactionDefinition initStatus = new DefaultTransactionDefinition(propagationBehavior); // aktivuje transakci transactionStatus = transactionManager.getTransaction(initStatus); } @After public final void afterTest() { manager.rollback(transactionStatus); }
Princíp je jednoduchý. Na začiatku testu sa začne transakcie. Po dokončení testu sa vykoná rollback.
Možno si hovoríte, prečo sa vykonáva rollback ... Ako sme sa dočítali v predchádzajúcich tutoriáloch, testy by sa nemali navzájom ovplyvňovať. Ak by sa dostala dáta do databázy, mohlo by dôjsť ku komplikáciám. Iný test by mohol začínať s dátami v stave, ktorý neočakáva.
Anotácie
@Transactional (propagation ...) V praxi Vám táto anotácia bohato postačí na všetku prácu s transakciami.
- Propagation. REQUIRED - DEFAULT. Ak existuje transakcia, je prevzaté. Ak neexistuje, je vytvorená nová.
- Propagation. MANDATORY - Ak existuje transakcie, je prevzaté. Ak neexistuje, vyhodí sa výnimka.
- Propagation. NOT_SUPPORTED - Pozastaví bežiaci transakciu a vykoná kód netransakčné.
- Propagation. SUPPORTED - Ak existuje transakcie, je prevzaté. Ak neexistuje, pustí sa kód netrasakčně.
- Propagation. REQUIRES_NEW - Ak existuje transakcie, je pozastavená a vytvorí sa nová. Ak neexistuje, vytvorí sa nová.
- Propagation. NEVER - Ak existuje transakcie, vyhodí výnimku. Inak sa vykoná netransakčné.
- Propagation. Nested - Ak existuje transakcie, pustí novú (beží 2 paralelne). Ak neexistuje, vytvorí sa.
Jednoduché použitie
Časť kódu, ktorá má bežať v transakcii je označená danú anotácií (@Transactional).
@Transactional public class CarService { public void myTransactionMethod() {} }
Ako to funguje?
Princíp si ukážeme na imaginárnom príklade.
Anotácia nám zaístí, že sa nad danou triedou vytvorí proxy objekt ( proxy pattern).
Daná metóda, čo má bežať v transakcii, je zaobalené do podobného bloku:
TransactionalManager txManager; public void myTransactionMethod(Object o) { try { TransactionStatus status = txManger.getTransaction(definition); myTransactionMethod(); txManager.commit(status); } catch (Exception e) { txManager.roolback(status); } }
pozn. Príklad vysvetľuje len princíp fungovania, v reále je to zložitejšie.
Kedykoľvek potom niekto zavolá danú triedu, zavolá sa tento proxy objekt a vykoná sa daný kód v transakcii.
Ak všetko prebehne v našej metóde v poriadku (nevyhodí sa výnimka), transakcie manager vykoná commit transakcie. Všetky príkazy sa vykonajú nad databázou.
Pokiaľ v našej metóde dôjde k chybe (napr. Kontrola duplicity záznamu vyhodí výnimku), manager vykoná roolback. Všetky príkazy po štarte transakcie sa zahodí.
Architektúra: Kde riadiť transakciu?
Čiže kde použiť @Transactional. Pokiaľ máte trojvrstvovú architektúru aplikácie, transakcia sa zvyčajne riadi na vrstve služby.
Trojvrstvová architektúra aplikácie:
- Controller - slúži pre komunikáciu s front-end. S tým čo vidí používateľ.
- Service - vrstva, kde prebiehajú hlavné výpočty aplikácie (tu sa používa @Transactional)
- DAO - vrstva pre prístup do repository (databázy)