Uvod u transakcije na Javi i proljeće

1. Uvod

U ovom ćemo uputstvu razumjeti što se podrazumijeva pod transakcijama na Javi. Tako ćemo razumjeti kako izvršiti lokalne i globalne transakcije resursa. To će nam također omogućiti da istražimo različite načine upravljanja transakcijama u Javi i Springu.

2. Što je transakcija?

Transakcije u Javi, kao i općenito, odnose se na niz radnji koje se sve moraju uspješno dovršiti. Stoga, ako jedna ili više radnji ne uspije, sve ostale radnje moraju se povući ostavljajući stanje aplikacije nepromijenjenim. To je neophodno kako bi se osiguralo da cjelovitost stanja aplikacije nikada ne bude ugrožena.

Također, ove transakcije mogu uključivati ​​jedan ili više resursa poput baze podataka, reda poruka, što dovodi do različitih načina izvođenja radnji u okviru transakcije. To uključuje obavljanje lokalnih transakcija resursa s pojedinačnim resursima. U globalnoj transakciji može sudjelovati više izvora.

3. Resursne lokalne transakcije

Prvo ćemo istražiti kako možemo koristiti transakcije u Javi dok radimo s pojedinačnim resursima. Evo, možda imamo više pojedinačnih radnji koje izvodimo s resursom poput baze podataka. Ali, možda bismo željeli da se dogode kao jedinstvena cjelina, kao u nedjeljivoj cjelini rada. Drugim riječima, želimo da se te radnje odvijaju u okviru jedne transakcije.

U Javi imamo nekoliko načina za pristup resursima poput baze podataka i rad s njima. Stoga način na koji radimo s transakcijama također nije isti. U ovom ćemo odjeljku pronaći kako možemo koristiti transakcije s nekim od ovih knjižnica u Javi koje se prilično često koriste.

3.1. JDBC

Povezivanje Java baze podataka (JDBC) je API u Javi koji definira kako pristupiti bazama podataka u Javi. Različiti dobavljači baze podataka pružaju JDBC pokretačke programe za povezivanje s bazom podataka na agentacijski način dobavljača. Dakle, dohvaćamo a Veza od vozača za obavljanje različitih operacija na bazi podataka:

JDBC nam pruža mogućnosti izvršavanja izvoda u okviru transakcije. The zadano ponašanje a Veza je automatsko predavanje. Da pojasnimo, što to znači je da se svaka pojedinačna izjava tretira kao transakcija i automatski se izvršava odmah nakon izvršenja.

Međutim, ako želimo grupirati više izvoda u jednoj transakciji, i to je moguće postići:

Veza veze = DriverManager.getConnection (CONNECTION_URL, USER, PASSWORD); pokušajte {connection.setAutoCommit (false); PreparedStatement firstStatement = veza .prepareStatement ("firstQuery"); firstStatement.executeUpdate (); PreparedStatement secondStatement = veza .prepareStatement ("secondQuery"); secondStatement.executeUpdate (); veza.commit (); } catch (Iznimka e) {connection.rollback (); }

Ovdje smo onemogućili način automatskog urezivanja za Veza. Dakle, možemo ručno definirati granicu transakcije i izvesti a počiniti ili vraćanje. JDBC nam također omogućuje postavljanje a Savepoint to nam pruža veću kontrolu nad koliko povratka.

3.2. JPA

Java Persistent API (JPA) je specifikacija u Javi na koju se može koristiti premostiti jaz između objektno orijentiranih modela domena i relacijskih sustava baza podataka. Dakle, postoji nekoliko implementacija JPA dostupnih od trećih strana poput Hibernate, EclipseLink i iBatis.

U JPA redovitu nastavu možemo definirati kao Entitet koja im pruža trajni identitet. The EntityManager klasa pruža potrebno sučelje za rad s više entiteta u kontekstu postojanosti. Kontekst postojanosti može se smatrati predmemorijom prve razine u kojoj se upravlja entitetima:

Kontekst postojanosti ovdje može biti dvije vrste, s opsegom transakcije ili s proširenim opsegom. Kontekst trajnosti s opsegom transakcija vezan je za jednu transakciju. Iako se kontekst trajnosti proširenog opsega može proširiti na više transakcija. The zadani opseg konteksta postojanosti je opseg transakcije.

Pogledajmo kako možemo stvoriti EntityManager i ručno definirajte granicu transakcije:

EntityManagerFactory entityManagerFactory = Perzistentnost.createEntityManagerFactory ("jpa-primjer"); EntityManager entityManager = entityManagerFactory.createEntityManager (); isprobajte {entityManager.getTransaction (). begin (); entityManager.persist (firstEntity); entityManager.persist (secondEntity); entityManager.getTransaction (). commit (); } catch (Exceotion e) {entityManager.getTransaction (). rollback (); }

Ovdje stvaramo EntityManager iz EntityManagerFactory u kontekstu konteksta trajnosti obuhvaćenog transakcijom. Tada definiramo granicu transakcije s početi, počiniti, i vraćanje metode.

3.3. JMS

Java Messaging Service (JMS) je specifikacija u Javi koja omogućuje aplikacijama asinkronu komunikaciju pomoću poruka. API nam omogućuje stvaranje, slanje, primanje i čitanje poruka iz reda ili teme. Postoji nekoliko usluga slanja poruka koje su u skladu sa JMS specifikacijama, uključujući OpenMQ i ActiveMQ.

JMS API podržava grupiranje više operacija slanja ili primanja u jednoj transakciji. Međutim, po prirodi arhitekture integracije zasnovane na porukama, izrada i potrošnja poruke ne mogu biti dio iste transakcije. Opseg transakcije ostaje između klijenta i JMS davatelja:

JMS nam omogućuje stvaranje a Sjednica od Veza koje dobivamo od dobavljača ConnectionFactory. Mi imamo mogućnost izrade a Sjednica koji se izvršava ili ne. Za ne-transakcije Sjednicas, možemo nadalje definirati i odgovarajući način potvrde.

Pogledajmo kako možemo stvoriti transakciju Sjednica za slanje više poruka u okviru transakcije:

ActiveMQConnectionFactory connectionFactory = nova ActiveMQConnectionFactory (CONNECTION_URL); Veza veze = = connectionFactory.createConnection (); connection.start (); pokušajte {Sesija sesije = connection.createSession (true, 0); Odredište = odredište = session.createTopic ("TEST.FOO"); MessageProducer proizvođač = session.createProducer (odredište); producent.send (firstMessage); producent.send (secondMessage); session.commit (); } catch (Iznimka e) {session.rollback (); }

Ovdje stvaramo MessageProducer za Odredište vrste teme. Dobivamo Odredište od Sjednica stvorili smo ranije. Dalje koristimo Sjednica definirati granice transakcija pomoću metoda počiniti i vraćanje.

4. Globalne transakcije

Kao što smo vidjeli, lokalne transakcije resursa omogućuju nam izvođenje više operacija unutar jednog resursa kao jedinstvene cjeline. Ali, vrlo često imamo posla s tim operacije koje se protežu na više resursa. Na primjer, rad u dvije različite baze podataka ili bazi podataka i redu poruka. Ovdje nam lokalna podrška za transakcije unutar resursa neće biti dovoljna.

Ono što nam treba u tim scenarijima je globalni mehanizam za razgraničenje transakcija koje obuhvaćaju više resursa koji sudjeluju. To je često poznato pod nazivom distribuirane transakcije i predložene su specifikacije za njihovo učinkovito rješavanje.

The XA specifikacija je jedna takva specifikacija koja definira upravitelja transakcija za kontrolu transakcija u više izvora. Java ima prilično zrelu podršku za distribuirane transakcije u skladu s XA specifikacijom kroz komponente JTA i JTS.

4.1. JTA

Java Transaction API (JTA) je Java Enterprise Edition API razvijen u okviru Java Community Process. To omogućuje Java programima i aplikacijskim poslužiteljima izvršavanje distribuiranih transakcija kroz XA resurse. JTA je oblikovan prema XA arhitekturi, koristeći dvofazno predavanje.

JTA određuje standardna Java sučelja između upravitelja transakcija i ostalih strana u distribuiranoj transakciji:

Razumijemo neka od ključnih sučelja koja su gore istaknuta:

  • TransactionManager: Sučelje koje omogućuje aplikacijskom poslužitelju da razgraniči i kontrolira transakcije
  • Korisnička transakcija: Ovo sučelje omogućuje aplikacijskom programu da eksplicitno razgraniči i kontrolira transakcije
  • XARizvor: Svrha ovog sučelja je omogućiti upravitelju transakcija rad s upraviteljima resursa za resurse kompatibilne s XA

4.2. JTS

Java Transaction Service (JTS) je specifikacija za izgradnju upravitelja transakcija koja se preslikava na OMG OTS specifikaciju. JTS koristi standardno sučelje CORBA ORB / TS i Internet Inter-ORB protokol (IIOP) za širenje konteksta transakcija između upravitelja transakcija JTS-a.

Na visokoj razini podržava Java Transaction API (JTA). JTS menadžer transakcija pruža transakcijske usluge stranama koje su uključene u distribuiranu transakciju:

Usluge koje JTS pruža aplikaciji u velikoj su mjeri transparentne i stoga ih možda i ne primjećujemo u arhitekturi aplikacije. JTS je oblikovan oko aplikacijskog poslužitelja koji apstrahira svu semantiku transakcija iz aplikacijskih programa.

5. JTA upravljanje transakcijama

Sada je vrijeme da shvatimo kako možemo upravljati distribuiranom transakcijom pomoću JTA-e. Distribuirane transakcije nisu trivijalna rješenja i stoga imaju i troškovne implikacije. Štoviše, postoji više opcija između kojih možemo uključiti JTA u našu aplikaciju. Stoga naš izbor mora biti s obzirom na cjelokupnu arhitekturu i težnje aplikacije.

5.1. JTA na poslužitelju aplikacija

Kao što smo vidjeli ranije, JTA arhitektura se oslanja aplikacijski poslužitelj za olakšavanje brojnih operacija povezanih s transakcijama. Jedna od ključnih usluga koje se oslanja na poslužitelj je usluga imenovanja putem JNDI. Tu su vezani i dohvaćeni izvori XA poput izvora podataka.

Osim toga, imamo izbor u pogledu načina na koji želimo upravljati granicom transakcije u našoj aplikaciji. To dovodi do dvije vrste transakcija unutar Java aplikacijskog poslužitelja:

  • Transakcija kojom upravlja kontejner: Kao što i samo ime govori, ovdje granicu transakcije postavlja poslužitelj aplikacija. Ovo pojednostavljuje razvoj Enterprise Java Beans-a (EJB) jer ne uključuje izjave povezane s razgraničenjem transakcija i oslanja se isključivo na spremnik. Međutim, to ne pruža dovoljnu fleksibilnost za aplikaciju.
  • Transakcija kojom upravlja grah: Suprotno transakciji kojom se upravlja spremnikom, u transakciji kojom se upravlja grahom EJB sadrže eksplicitne izjave za definiranje razgraničenja transakcije. To pruža preciznu kontrolu aplikacije pri označavanju granica transakcije, iako po cijenu složenosti.

Jedan od glavnih nedostataka obavljanja transakcija u kontekstu aplikacijskog poslužitelja je taj aplikacija postaje usko povezana s poslužiteljem. To ima implikacije na provjerljivost, upravljivost i prenosivost aplikacije. Ovo je dublje u mikroservisnoj arhitekturi gdje je naglasak više na razvoju poslužiteljski neutralnih aplikacija.

5.2. JTA Samostalno

Problemi o kojima smo raspravljali u prošlom odjeljku dali su golem zamah za stvaranje rješenja za distribuirane transakcije koja se ne oslanjaju na aplikacijski poslužitelj. U tom nam je pogledu dostupno nekoliko opcija, poput upotrebe podrške za transakcije s Springom ili upotrebe upravitelja transakcija poput Atomikosa.

Pogledajmo kako možemo koristiti upravitelj transakcija poput Atomikosa da olakšamo distribuiranu transakciju s bazom podataka i redom poruka. Jedan od ključnih aspekata distribuirane transakcije je prijavljivanje i uklanjanje resursa koji sudjeluju s monitorom transakcija. Za nas se brine Atomikos. Sve što trebamo je koristiti apstrakcije koje osigurava Atomikos:

AtomikosDataSourceBean atomikosDataSourceBean = novo AtomikosDataSourceBean (); atomikosDataSourceBean.setXaDataSourceClassName ("com.mysql.cj.jdbc.MysqlXADataSource"); DataSource dataSource = atomikosDataSourceBean;

Ovdje stvaramo primjerak AtomikosDataSourceBean i registriranje dobavljača XADataSource. Odsad nadalje, možemo nastaviti koristiti ovo kao i svako drugo Izvor podataka i iskoristite pogodnosti distribuiranih transakcija.

Slično tome, i mi imaju apstrakciju za red poruka koja se brine o automatskoj registraciji XA resursa specifičnog za dobavljača s monitorom transakcija:

AtomikosConnectionFactoryBean atomikosConnectionFactoryBean = novo AtomikosConnectionFactoryBean (); atomikosConnectionFactoryBean.setXaConnectionFactory (nova ActiveMQXAConnectionFactory ()); ConnectionFactory connectionFactory = atomikosConnectionFactoryBean;

Ovdje stvaramo primjerak AtomikosConnectionFactoryBean i registriranje XAConnectionFactory od dobavljača JMS-a s omogućenom XA. Nakon ovoga, možemo nastaviti koristiti ovo kao redovno ConnectionFactory.

Sada nam Atomikos pruža posljednji dio slagalice koji će spojiti sve, primjerice Korisnička transakcija:

UserTransaction userTransaction = novi UserTransactionImp ();

Sada smo spremni stvoriti aplikaciju s distribuiranom transakcijom koja se proteže kroz našu bazu podataka i red poruka:

isprobajte {userTransaction.begin (); java.sql.Connection dbConnection = dataSource.getConnection (); PreparedStatement pripremljenStatement = dbConnection.prepareStatement (SQL_INSERT); PreparedStatement.executeUpdate (); javax.jms.Connection mbConnection = connectionFactory.createConnection (); Sjednica sesije = mbConnection.createSession (true, 0); Odredište odredišta = session.createTopic ("TEST.FOO"); MessageProducer proizvođač = session.createProducer (odredište); producent.send (PORUKA); userTransaction.commit (); } catch (Iznimka e) {userTransaction.rollback (); }

Ovdje koristimo metode početi i počiniti u razredu Korisnička transakcija za razgraničenje granice transakcije. To uključuje spremanje zapisa u bazu podataka, kao i objavljivanje poruke u redu poruka.

6. Podrška transakcijama u proljeće

To smo vidjeli rukovanje transakcijama prilično je uključen zadatak koji uključuje puno kodiranja i konfiguracije. Štoviše, svaki resurs ima svoj način rukovanja lokalnim transakcijama. U Javi nas JTA apstrahira od ovih varijacija, ali dalje donosi pojedinosti davatelja usluga i složenost aplikacijskog poslužitelja.

Proljetna platforma pruža nam mnogo čišći način rukovanja transakcijama, kako resursnim lokalnim tako i globalnim transakcijama na Javi. To zajedno s ostalim prednostima Springa stvara uvjerljiv slučaj korištenja Springa za rukovanje transakcijama. Štoviše, prilično je jednostavno konfigurirati i prebaciti upravitelja transakcija s Springom, koji može biti poslužitelj ili samostalni.

Proljeće nam ovo pruža besprijekorna apstrakcija stvaranjem proxyja za metode s transakcijskim kodom. Proxy upravlja stanjem transakcije u ime koda uz pomoć TransactionManager:

Ovdje je središnje sučelje PlatformTransactionManager koja ima na raspolaganju niz različitih implementacija. Pruža apstrakcije preko JDBC (DataSource), JMS, JPA, JTA i mnogih drugih resursa.

6.1. Konfiguracije

Pogledajmo kako možemo konfigurirati Proljeće za korištenje Atomikosa kao upravitelja transakcija i pružanja transakcijske podrške za JPA i JMS. Počet ćemo definiranjem a PlatformTransactionManager tipa JTA:

@Bean public PlatformTransactionManager platformTransactionManager () baca Throwable {return new JtaTransactionManager (userTransaction () ,actionManager ()); }

Ovdje navodimo primjere Korisnička transakcija i TransactionManager do JTATransactionManager. Ove primjerke pruža knjižnica upravitelja transakcija poput Atomikosa:

@Bean public UserTransaction userTransaction () {return new UserTransactionImp (); } @Bean (initMethod = "init", killMethod = "close") public TransactionManageractionManager () {return new UserTransactionManager (); }

Razredi UserTransactionImp i UserTransactionManager ovdje ih pruža Atomikos.

Dalje, moramo definirati JmsTemplete koja osnovna klasa omogućava sinkroni JMS pristup u proljeće:

@Bean public JmsTemplate jmsTemplate () baca Throwable {return new JmsTemplate (connectionFactory ()); }

Ovdje, ConnectionFactory pruža Atomikos gdje omogućuje distribuiranu transakciju za Veza pruža:

@Bean (initMethod = "init", killMethod = "close") public ConnectionFactory connectionFactory () {ActiveMQXAConnectionFactory activeMQXAConnectionFactory = new ActiveMQXAConnectionFactory (); activeMQXAConnectionFactory.setBrokerURL ("tcp: // localhost: 61616"); AtomikosConnectionFactoryBean atomikosConnectionFactoryBean = novo AtomikosConnectionFactoryBean (); atomikosConnectionFactoryBean.setUniqueResourceName ("xamq"); atomikosConnectionFactoryBean.setLocalTransactionMode (false); atomikosConnectionFactoryBean.setXaConnectionFactory (activeMQXAConnectionFactory); povratak atomikosConnectionFactoryBean; }

Dakle, kao što vidimo, ovdje prebacujemo JMS davatelja usluga XAConnectionFactory s AtomikosConnectionFactoryBean.

Dalje, moramo definirati AbstractEntityManagerFactoryBean koja je odgovorna za stvaranje JPA EntityManagerFactory grah u proljeće:

@Bean public LocalContainerEntityManagerFactoryBean entityManager () baca SQLException {LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean (); entityManager.setDataSource (dataSource ()); Svojstva svojstva = new Svojstva (); svojstva.setProperty ("javax.persistence.transactionType", "jta"); entityManager.setJpaProperties (svojstva); return entityManager; }

Kao i prije, Izvor podataka koje smo postavili u LocalContainerEntityManagerFactoryBean ovdje pruža Atomikos s omogućenim distribuiranim transakcijama:

@Bean (initMethod = "init", killMethod = "close") javni DataSource dataSource () baca SQLException {MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource (); mysqlXaDataSource.setUrl ("jdbc: mysql: //127.0.0.1: 3306 / test"); AtomikosDataSourceBean xaDataSource = novo AtomikosDataSourceBean (); xaDataSource.setXaDataSource (mysqlXaDataSource); xaDataSource.setUniqueResourceName ("xads"); return xaDataSource; }

I ovdje prekrivamo davatelja usluga XADataSource u AtomikosDataSourceBean.

6.2. Upravljanje transakcijama

Nakon što smo prošli sve konfiguracije u posljednjem odjeljku, moramo se osjećati prilično shrvano! Možda ipak dovedemo u pitanje prednosti korištenja proljeća. Ali ne zaboravite da sva ova konfiguracija ima omogućili su nam apstrakciju s većine tipskih ploča za davatelje usluga a naš stvarni kod aplikacije uopće toga ne mora biti svjestan.

Dakle, sada smo spremni istražiti kako koristiti transakcije u proljeće gdje namjeravamo ažurirati bazu podataka i objaviti poruke. Proljeće nam pruža dva načina da to postignemo s vlastitim prednostima koje možemo birati. Razumijemo kako ih možemo iskoristiti:

  • Deklarativna podrška

Transakcije u Proljeće najlakše je koristiti deklarativnom podrškom. Evo, imamo prikladna napomena dostupna za primjenu na metodi ili čak na predavanju. To jednostavno omogućuje globalnu transakciju za naš kod:

@PersistenceContext EntityManager entityManager; @Autowired JmsTemplate jmsTemplate; @Transational (propagation = Propagation.REQUIRED) javni void postupak (ENTITY, PORUKA) {entityManager.persist (ENTITY); jmsTemplate.convertAndSend (DESTINACIJA, PORUKA); }

Jednostavan gornji kod dovoljan je da omogući operaciju spremanja u bazi podataka i operaciju objavljivanja u redu poruka unutar JTA transakcije.

  • Programska podrška

Iako je deklarativna podrška prilično elegantna i jednostavna, ne nudi nam je korist od preciznijeg nadzora granice transakcije. Stoga, ako imamo određenu potrebu da to postignemo, Spring nudi programsku podršku za određivanje granice transakcije:

@Autowired privatni PlatformTransactionManageractionManager; javni void postupak (ENTITY, PORUKA) {TransactionTemplateactionTemplate = novi TransactionTemplate (transactionManager); actionTemplate.executeWithoutResult (status -> {entityManager.persist (ENTITY); jmsTemplate.convertAndSend (DESTINATION, PORUKA);}); }

Dakle, kao što vidimo, moramo stvoriti TransactionTemplate s dostupnim PlatformTransactionManager. Tada možemo koristiti TransactionTemplete za obradu hrpe izjava unutar globalne transakcije.

7. Naknadne misli

Kao što smo vidjeli da je rukovanje transakcijama, posebno onima koje se protežu kroz više resursa, složeno. Štoviše, transakcije u biti blokiraju, što je štetno za kašnjenje i protok aplikacije. Nadalje, testiranje i održavanje koda s distribuiranim transakcijama nije lako, pogotovo ako transakcija ovisi o osnovnom poslužitelju aplikacija. Dakle, sve u svemu, najbolje je uopće izbjegavati transakcije ako možemo!

Ali to je daleko od stvarnosti. Ukratko, u stvarnim aplikacijama često imamo legitimnu potrebu za transakcijama. Iako je moguće preispitivanje arhitekture aplikacije bez transakcija, možda nije uvijek moguće. Stoga moramo usvojiti određene najbolje prakse prilikom rada s transakcijama na Javi kako bismo poboljšali svoje aplikacije:

  • Jedan od temeljnih pomaka koji bismo trebali usvojiti jest koristite samostalne upravitelje transakcija umjesto onih koje pruža poslužitelj aplikacija. Samo ovo može uvelike pojednostaviti našu aplikaciju. Štoviše, vrlo je pogodan za arhitekturu mikro-usluga izvornu za oblak.
  • Unaprijediti, sloj apstrakcije poput Proljeća može nam pomoći u suzbijanju izravnog utjecaja pružatelja usluga poput pružatelja usluga JPA ili JTA. Dakle, ovo nam može omogućiti prebacivanje između davatelja usluga bez puno utjecaja na našu poslovnu logiku. Štoviše, uklanja nam odgovornosti na niskoj razini upravljanja stanjem transakcije.
  • Napokon, trebali bismo biti oprezni pri odabiru granice transakcije u našem kodu. Budući da transakcije blokiraju, uvijek je bolje ograničiti transakciju što je moguće ograničenije. Ako je potrebno, za transakcije bismo trebali preferirati programsku nego deklarativnu kontrolu.

8. Zaključak

Da rezimiramo, u ovom smo tutorijalu razgovarali o transakcijama u kontekstu Jave. Prošli smo podršku za lokalne transakcije pojedinačnih resursa u Javi za različite resurse. Također smo prošli načine za postizanje globalnih transakcija u Javi.

Nadalje, prošli smo različite načine upravljanja globalnim transakcijama u Javi. Također, shvatili smo kako nam Spring olakšava korištenje transakcija u Javi.

Konačno, prošli smo nekoliko najboljih praksi rada s transakcijama na Javi.