Introduksjon til transaksjoner i Java og vår

1. Introduksjon

I denne opplæringen vil vi forstå hva som menes med transaksjoner i Java. Dermed vil vi forstå hvordan vi kan utføre ressurslokale transaksjoner og globale transaksjoner. Dette vil også tillate oss å utforske forskjellige måter å håndtere transaksjoner i Java og Spring på.

2. Hva er en transaksjon?

Transaksjoner i Java, som generelt refererer til en rekke handlinger som alle må fullføres. Derfor, hvis en eller flere handlinger mislykkes, må alle andre handlinger trekke tilbake og la tilstanden til søknaden være uendret. Dette er nødvendig for å sikre at applikasjonstilstandens integritet aldri kompromitteres.

Disse transaksjonene kan også involvere en eller flere ressurser som database, meldingskø, noe som gir opphav til forskjellige måter å utføre handlinger under en transaksjon. Disse inkluderer å utføre lokale ressurstransaksjoner med individuelle ressurser. Alternativt kan flere ressurser delta i en global transaksjon.

3. Ressurs lokale transaksjoner

Vi vil først undersøke hvordan vi kan bruke transaksjoner i Java mens vi arbeider med individuelle ressurser. Her kan vi ha flere individuelle handlinger som vi utfører med en ressurs som en database. Men vi vil kanskje at de skal skje som en samlet helhet, som i en udelelig arbeidsenhet. Med andre ord, vi vil at disse handlingene skal skje under en enkelt transaksjon.

I Java har vi flere måter å få tilgang til og bruke en ressurs som en database. Derfor er ikke måten vi håndterer transaksjoner på er den samme. I denne delen finner vi hvordan vi kan bruke transaksjoner med noen av disse bibliotekene i Java som ofte brukes.

3.1. JDBC

Java Database Connectivity (JDBC) er API i Java som definerer hvordan du får tilgang til databaser i Java. Ulike databaseleverandører tilbyr JDBC-drivere for å koble til databasen på en leverandør-agnostisk måte. Så, vi henter en Forbindelse fra en driver for å utføre forskjellige operasjoner på databasen:

JDBC gir oss mulighetene til å utføre uttalelser under en transaksjon. De standard oppførsel av en Forbindelse er automatisk forpliktet. For å avklare, hva dette betyr er at hver eneste uttalelse blir behandlet som en transaksjon og automatisk blir begått rett etter gjennomføring.

Men hvis vi ønsker å samle flere uttalelser i en enkelt transaksjon, er dette også mulig å oppnå:

Tilkoblingstilkobling = DriverManager.getConnection (CONNECTION_URL, USER, PASSWORD); prøv {connection.setAutoCommit (false); PreparedStatement firstStatement = forbindelse .prepareStatement ("firstQuery"); firstStatement.executeUpdate (); PreparedStatement secondStatement = forbindelse .prepareStatement ("secondQuery"); secondStatement.executeUpdate (); connection.commit (); } fange (Unntak e) {connection.rollback (); }

Her har vi deaktivert auto-commit-modusen for Forbindelse. Derfor kan vi definere transaksjonsgrensen manuelt og utføre en begå eller tilbakeføring. JDBC lar oss også sette en Savepoint som gir oss mer kontroll over hvor mye vi skal returnere.

3.2. JPA

Java Persistence API (JPA) er en spesifikasjon i Java som kan brukes til bygge bro over gapet mellom objektorienterte domenemodeller og relasjonelle databasesystemer. Så det er flere implementeringer av JPA tilgjengelig fra tredjeparter som Hibernate, EclipseLink og iBatis.

I JPA kan vi definere vanlige klasser som en Enhet som gir dem vedvarende identitet. De EntityManager klasse gir det nødvendige grensesnittet for å jobbe med flere enheter innenfor en utholdenhetskontekst. Persistens-konteksten kan betraktes som et hurtignivåbuffer der enheter administreres:

Vedholdenhetskonteksten her kan være av to typer, transaksjonsomfang eller utvidet omfang. En transaksjonsomfanget utholdenhetskontekst er bundet til en enkelt transaksjon. Mens utholdenhetskonteksten med utvidet omfang kan strekke seg over flere transaksjoner. De standard omfang av en utholdenhetskontekst er transaksjonsomfang.

La oss se hvordan vi kan lage en EntityManager og definere en transaksjonsgrense manuelt:

EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory ("jpa-eksempel"); EntityManager entityManager = entityManagerFactory.createEntityManager (); prøv {entityManager.getTransaction (). start (); entityManager.persist (firstEntity); entityManager.persist (secondEntity); entityManager.getTransaction (). commit (); } catch (Exceotion e) {entityManager.getTransaction (). rollback (); }

Her lager vi en EntityManager fra EntityManagerFactory innenfor konteksten av en transaksjonsomfanget utholdenhetskontekst. Så definerer vi transaksjonsgrensen med begynne, begå, og tilbakeføring metoder.

3.3. JMS

Java Messaging Service (JMS) er en spesifikasjon i Java som lar applikasjoner kommunisere asynkront ved hjelp av meldinger. API-et lar oss lage, sende, motta og lese meldinger fra en kø eller et emne. Det er flere meldingstjenester som samsvarer med JMS-spesifikasjoner, inkludert OpenMQ og ActiveMQ.

JMS API støtter samling av flere send- eller mottaksoperasjoner i en enkelt transaksjon. Imidlertid, av karakteren av meldingsbasert integrasjonsarkitektur, produksjon og forbruk av en melding kan ikke være en del av den samme transaksjonen. Transaksjonens omfang forblir mellom klienten og JMS-leverandøren:

JMS lar oss lage en Økt fra en Forbindelse som vi får fra en leverandørspesifikk ConnectionFactory. Vi har en alternativ for å opprette en Økt som er inngått eller ikke. For ikke-transaksjon Økts, vi kan også definere en passende bekreftelsesmodus.

La oss se hvordan vi kan lage en inngått Økt å sende flere meldinger under en transaksjon:

ActiveMQConnectionFactory connectionFactory = ny ActiveMQConnectionFactory (CONNECTION_URL); Tilkoblingstilkobling = = connectionFactory.createConnection (); connection.start (); prøv {Session session = connection.createSession (true, 0); Destinasjon = destinasjon = session.createTopic ("TEST.FOO"); MessageProducer produsent = session.createProducer (destinasjon); produsent.send (firstMessage); produsent.send (secondMessage); session.commit (); } fange (Unntak e) {session.rollback (); }

Her lager vi en MessageProducer for Mål av typen tema. Vi får Mål fra Økt vi opprettet tidligere. Vi bruker videre Økt å definere transaksjonsgrenser ved hjelp av metodene begå og tilbakeføring.

4. Globale transaksjoner

Som vi så ressurs lokale transaksjoner tillate oss å utføre flere operasjoner innen en enkelt ressurs som en samlet helhet. Men ganske ofte håndterer vi det operasjoner som spenner over flere ressurser. For eksempel drift i to forskjellige databaser eller en database og en meldingskø. Her vil ikke lokal transaksjonsstøtte innen ressurser være tilstrekkelig for oss.

Det vi trenger i disse scenariene er en global mekanisme for å avgrense transaksjoner som strekker seg over flere deltakende ressurser. Dette er ofte kjent som distribuerte transaksjoner, og det er spesifikasjoner som er foreslått for å håndtere dem effektivt.

De XA-spesifikasjon er en slik spesifikasjon som definerer en transaksjonssjef for å kontrollere transaksjonen på tvers av flere ressurser. Java har ganske moden støtte for distribuerte transaksjoner i samsvar med XA-spesifikasjonen gjennom komponentene JTA og JTS.

4.1. JTA

Java Transaction API (JTA) er en Java Enterprise Edition API utviklet under Java Community Process. Den gjør det mulig for Java-applikasjoner og applikasjonsservere å utføre distribuerte transaksjoner på tvers av XA-ressurser. JTA er modellert rundt XA-arkitektur, og utnytter to-fase forpliktelse.

JTA spesifiserer standard Java-grensesnitt mellom en transaksjonsbehandling og de andre partene i en distribuert transaksjon:

La oss forstå noen av nøkkelgrensesnittene fremhevet ovenfor:

  • TransactionManager: Et grensesnitt som lar en applikasjonsserver avgrense og kontrollere transaksjoner
  • UserTransaction: Dette grensesnittet gjør det mulig for et applikasjonsprogram å avgrense og kontrollere transaksjoner eksplisitt
  • XAResource: Hensikten med dette grensesnittet er å tillate en transaksjonsleder å jobbe med ressursansvarlige for XA-kompatible ressurser

4.2. JTS

Java Transaction Service (JTS) er en spesifikasjon for å bygge transaksjonssjefen som tilordnes til OMG OTS-spesifikasjonen. JTS bruker standard CORBA ORB / TS-grensesnitt og Internet Inter-ORB Protocol (IIOP) for spredning av transaksjonskontekst mellom JTS-transaksjonsledere.

På et høyt nivå støtter den Java Transaction API (JTA). En JTS-transaksjonsansvarlig tilbyr transaksjonstjenester til partene som er involvert i en distribuert transaksjon:

Tjenester som JTS leverer til et program er stort sett gjennomsiktige, og derfor legger vi kanskje ikke engang merke til dem i applikasjonsarkitekturen. JTS er arkitert rundt en applikasjonsserver som trekker ut all transaksjonssemantikk fra applikasjonsprogrammene.

5. JTA Transaction Management

Nå er det på tide å forstå hvordan vi kan administrere en distribuert transaksjon ved hjelp av JTA. Distribuerte transaksjoner er ikke trivielle løsninger og har dermed også kostnadskonsekvenser. Videre det er flere alternativer som vi kan velge mellom for å inkludere JTA i applikasjonen vår. Derfor må vårt valg være i lys av den generelle applikasjonsarkitekturen og ambisjonene.

5.1. JTA i Application Server

Som vi har sett tidligere, er JTA-arkitektur avhengig av applikasjonsserveren for å lette en rekke transaksjonsrelaterte operasjoner. En av nøkkeltjenestene den stoler på at serveren skal tilby, er en navngivningstjeneste gjennom JNDI. Det er her XA-ressurser som datakilder er bundet til og hentet fra.

Bortsett fra dette har vi et valg når det gjelder hvordan vi vil håndtere transaksjonsgrensen i søknaden vår. Dette gir opphav til to typer transaksjoner innen Java-applikasjonsserveren:

  • Beholderstyrt transaksjon: Som navnet antyder, her er transaksjonsgrensen satt av applikasjonsserveren. Dette forenkler utviklingen av Enterprise Java Beans (EJB), da den ikke inkluderer utsagn knyttet til transaksjonsavgrensning og bare er avhengig av containeren for å gjøre det. Dette gir imidlertid ikke nok fleksibilitet for applikasjonen.
  • Bønnestyrt transaksjon: I motsetning til den containerstyrte transaksjonen, i en bønnestyrt transaksjon EJB inneholder eksplisitte uttalelser for å definere transaksjonsavgrensningen. Dette gir presis kontroll over applikasjonen i å markere grensene for transaksjonen, om enn på bekostning av mer kompleksitet.

En av de største ulempene ved å utføre transaksjoner i sammenheng med en applikasjonsserver er at søknaden blir tett koblet med serveren. Dette har implikasjoner med hensyn til testbarhet, håndterbarhet og bærbarhet av applikasjonen. Dette er mer dyptgående i mikroservicearkitektur hvor vekten er mer på å utvikle serverneutrale applikasjoner.

5.2. JTA frittstående

Problemene vi diskuterte i forrige avsnitt har gitt et enormt momentum for å skape løsninger for distribuerte transaksjoner som ikke er avhengig av en applikasjonsserver. Det er flere alternativer tilgjengelig for oss i denne forbindelse, som å bruke transaksjonsstøtte med Spring eller bruke en transaksjonsleder som Atomikos.

La oss se hvordan vi kan bruke en transaksjonsansvarlig som Atomikos for å legge til rette for en distribuert transaksjon med en database og en meldingskø. En av de viktigste aspektene ved en distribuert transaksjon er verve og avnotere deltakerressursene med transaksjonsovervåkeren. Atomikos tar seg av dette for oss. Alt vi trenger å gjøre er å bruke abstraksjoner fra Atomikos:

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

Her lager vi en forekomst av AtomikosDataSourceBean og registrere den leverandørspesifikke XADataSource. Herfra kan vi fortsette å bruke dette som alle andre Datakilde og få fordelene ved distribuerte transaksjoner.

Tilsvarende vi ha en abstraksjon for meldingskø som tar seg av å registrere den leverandørspesifikke XA-ressursen i transaksjonsmonitoren automatisk:

AtomikosConnectionFactoryBean atomikosConnectionFactoryBean = ny AtomikosConnectionFactoryBean (); atomikosConnectionFactoryBean.setXaConnectionFactory (ny ActiveMQXAConnectionFactory ()); ConnectionFactory connectionFactory = atomikosConnectionFactoryBean;

Her lager vi en forekomst av AtomikosConnectionFactoryBean og registrere XAConnectionFactory fra en XA-aktivert JMS-leverandør. Etter dette kan vi fortsette å bruke dette som en vanlig ConnectionFactory.

Nå gir Atomikos oss den siste brikken i puslespillet for å bringe alt sammen, en forekomst av UserTransaction:

UserTransaction userTransaction = ny UserTransactionImp ();

Nå er vi klare til å lage en applikasjon med distribuert transaksjon som spenner over databasen vår og meldingskøen:

prøv {userTransaction.begin (); java.sql.Connection dbConnection = dataSource.getConnection (); PreparedStatement preparedStatement = dbConnection.prepareStatement (SQL_INSERT); preparedStatement.executeUpdate (); javax.jms.Connection mbConnection = connectionFactory.createConnection (); Sessionsøkt = mbConnection.createSession (true, 0); Destinasjonsdestinasjon = session.createTopic ("TEST.FOO"); MessageProducer produsent = session.createProducer (destinasjon); produsent.send (MESSAGE); userTransaction.commit (); } fange (Unntak e) {userTransaction.rollback (); }

Her bruker vi metoder begynne og begå i klassen UserTransaction for å avgrense transaksjonsgrensen. Dette inkluderer lagring av en post i databasen, samt publisering av en melding i meldingskøen.

6. Transaksjonsstøtte om våren

Vi har sett det håndtering av transaksjoner er snarere en involvert oppgave som inkluderer mye koding av kjele og konfigurasjoner. Videre har hver ressurs sin egen måte å håndtere lokale transaksjoner på. I Java trekker JTA oss fra disse variantene, men gir videre leverandørspesifikke detaljer og kompleksiteten til applikasjonsserveren.

Vårplattform gir oss en mye renere måte å håndtere transaksjoner på, både lokale ressurser og globale transaksjoner i Java. Dette sammen med de andre fordelene med Spring skaper en overbevisende sak for å bruke Spring til å håndtere transaksjoner. Videre er det ganske enkelt å konfigurere og bytte en transaksjonsbehandling med Spring, som kan leveres av serveren eller frittstående.

Våren gir oss dette sømløs abstraksjon ved å lage en proxy for metodene med transaksjonskode. Fullmakten administrerer transaksjonstilstanden på vegne av koden ved hjelp av TransactionManager:

Det sentrale grensesnittet her er PlatformTransactionManager som har en rekke forskjellige implementeringer tilgjengelig. Det gir abstraksjoner over JDBC (DataSource), JMS, JPA, JTA og mange andre ressurser.

6.1. Konfigurasjoner

La oss se hvordan vi kan konfigurere Våren å bruke Atomikos som transaksjonsleder og tilby transaksjonsstøtte for JPA og JMS. Vi begynner med å definere en PlatformTransactionManager av typen JTA:

@Bean offentlig PlatformTransactionManager platformTransactionManager () kaster Throwable {returner ny JtaTransactionManager (userTransaction (), transactionManager ()); }

Her gir vi eksempler på UserTransaction og TransactionManager til JTATransactionManager. Disse forekomstene er levert av et transaksjonsadministratorbibliotek som Atomikos:

@Bean offentlig UserTransaction userTransaction () {returner ny UserTransactionImp (); } @Bean (initMethod = "init", destroyMethod = "close") offentlig TransactionManager transactionManager () {returner ny UserTransactionManager (); }

Klassene UserTransactionImp og UserTransactionManager er levert av Atomikos her.

Videre må vi definere JmsTemplete som kjerneklassen tillater synkron JMS-tilgang om våren:

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

Her, ConnectionFactory leveres av Atomikos der det muliggjør distribuert transaksjon for Forbindelse levert av det:

@Bean (initMethod = "init", destroyMethod = "close") offentlig ConnectionFactory connectionFactory () {ActiveMQXAConnectionFactory activeMQXAConnectionFactory = ny ActiveMQXAConnectionFactory (); activeMQXAConnectionFactory.setBrokerURL ("tcp: // localhost: 61616"); AtomikosConnectionFactoryBean atomikosConnectionFactoryBean = ny AtomikosConnectionFactoryBean (); atomikosConnectionFactoryBean.setUniqueResourceName ("xamq"); atomikosConnectionFactoryBean.setLocalTransactionMode (false); atomikosConnectionFactoryBean.setXaConnectionFactory (activeMQXAConnectionFactory); returnere atomikosConnectionFactoryBean; }

Så, som vi kan se, pakker vi inn en JMS-leverandørspesifikk XAConnectionFactory med AtomikosConnectionFactoryBean.

Deretter må vi definere en AbstractEntityManagerFactoryBean som er ansvarlig for å lage JPA EntityManagerFactory bønne om våren:

@Bean offentlig LocalContainerEntityManagerFactoryBean entityManager () kaster SQLException {LocalContainerEntityManagerFactoryBean entityManager = ny LocalContainerEntityManagerFactoryBean (); entityManager.setDataSource (dataSource ()); Egenskaper egenskaper = nye egenskaper (); properties.setProperty ("javax.persistence.transactionType", "jta"); entityManager.setJpaProperties (egenskaper); retur enhetManager; }

Som før, den Datakilde som vi setter i LocalContainerEntityManagerFactoryBean her er levert av Atomikos med distribuerte transaksjoner aktivert:

@Bean (initMethod = "init", destroyMethod = "close") offentlig DataSource dataSource () kaster SQLException {MysqlXADataSource mysqlXaDataSource = ny MysqlXADataSource (); mysqlXaDataSource.setUrl ("jdbc: mysql: //127.0.0.1: 3306 / test"); AtomikosDataSourceBean xaDataSource = ny AtomikosDataSourceBean (); xaDataSource.setXaDataSource (mysqlXaDataSource); xaDataSource.setUniqueResourceName ("xads"); returner xaDataSource; }

Også her pakker vi inn leverandørspesifikke XADataSource i AtomikosDataSourceBean.

6.2. Transaksjonsstyring

Etter å ha gått gjennom alle konfigurasjonene i den siste delen, må vi føle oss ganske overveldede! Vi kan til og med stille spørsmål ved fordelene med å bruke vår. Men husk at all denne konfigurasjonen har muliggjorde oss abstraksjon fra det meste av den leverandørspesifikke kjeleplaten og vår faktiske søknadskode trenger ikke å være klar over det i det hele tatt.

Så nå er vi klare til å utforske hvordan vi kan bruke transaksjoner om våren der vi har tenkt å oppdatere databasen og publisere meldinger. Våren gir oss to måter å oppnå dette med sine egne fordeler å velge mellom. La oss forstå hvordan vi kan bruke dem:

  • Deklarativ støtte

Den enkleste måten å bruke transaksjoner på våren er med deklarativ støtte. Her har vi det en bekvemmelighetskommentar tilgjengelig for bruk på metoden eller til og med i klassen. Dette muliggjør ganske enkelt global transaksjon for koden vår:

@PersistenceContext EntityManager entityManager; @Autowired JmsTemplate jmsTemplate; @Transactional (propagation = Propagation.REQUIRED) offentlig ugyldig prosess (ENTITY, MESSAGE) {entityManager.persist (ENTITY); jmsTemplate.convertAndSend (DESTINASJON, MELDING); }

Den enkle koden ovenfor er tilstrekkelig for å tillate lagring i databasen og publiseringsoperasjon i meldingskø i en JTA-transaksjon.

  • Programmatisk støtte

Selv om den deklarative støtten er ganske elegant og enkel, tilbyr den oss ikke fordelen av å kontrollere transaksjonsgrensen mer presist. Derfor, hvis vi har et visst behov for å oppnå det, tilbyr Spring programmatisk støtte for å avgrense transaksjonsgrensen:

@Autowired privat PlatformTransactionManager transactionManager; offentlig ugyldig prosess (ENTITY, MESSAGE) {TransactionTemplate transactionTemplate = ny TransactionTemplate (transactionManager); transactionTemplate.executeWithoutResult (status -> {entityManager.persist (ENTITY); jmsTemplate.convertAndSend (DESTINATION, MESSAGE);}); }

Så som vi kan se, må vi lage en TransactionTemplate med det tilgjengelige PlatformTransactionManager. Da kan vi bruke TransactionTemplete å behandle en rekke uttalelser innen en global transaksjon.

7. Ettertanke

Som vi har sett at håndtering av transaksjoner, spesielt de som spenner over flere ressurser, er komplekse. Videre transaksjoner blokkerer iboende, noe som er skadelig for ventetid og gjennomstrømning av en søknad. Videre er det ikke lett å teste og vedlikeholde kode med distribuerte transaksjoner, spesielt hvis transaksjonen avhenger av den underliggende applikasjonsserveren. Så alt i alt er det best å unngå transaksjoner i det hele tatt hvis vi kan!

Men det er langt fra virkeligheten. Kort sagt, i virkelige applikasjoner har vi ofte et legitimt behov for transaksjoner. Selv om det er det mulig å revurdere applikasjonsarkitekturen uten transaksjoner, det er ikke alltid mulig. Derfor må vi ta i bruk visse gode fremgangsmåter når vi arbeider med transaksjoner i Java for å gjøre applikasjonene våre bedre:

  • En av de grunnleggende skiftene vi bør ta i bruk er å bruke frittstående transaksjonsledere i stedet for de som leveres av en applikasjonsserver. Dette alene kan forenkle søknaden vår sterkt. Videre er det veldig godt egnet for sky-native mikroservicearkitektur.
  • Lengre, et abstraksjonslag som våren kan hjelpe oss med å få direkte innvirkning fra leverandører som JPA- eller JTA-leverandører. Så dette kan gjøre det mulig for oss å bytte mellom leverandører uten stor innvirkning på forretningslogikken vår. Videre tar det fra oss lavt ansvar for å administrere transaksjonstilstanden.
  • Til slutt burde vi være det nøye med å velge transaksjonsgrensen i koden vår. Siden transaksjoner blokkerer, er det alltid bedre å holde transaksjonsgrensen så begrenset som mulig. Om nødvendig bør vi foretrekke programmatisk fremfor deklarativ kontroll for transaksjoner.

8. Konklusjon

For å oppsummere, i denne opplæringen diskuterte vi transaksjoner i sammenheng med Java. Vi gikk gjennom støtte for individuelle ressurslokale transaksjoner i Java for forskjellige ressurser. Vi gikk også gjennom måtene å oppnå globale transaksjoner på Java.

Videre gikk vi gjennom forskjellige måter å administrere globale transaksjoner i Java. Vi forsto også hvordan Spring gjør det enklere for oss å bruke transaksjoner i Java.

Til slutt gikk vi gjennom noen av de beste metodene når vi jobbet med transaksjoner i Java.


$config[zx-auto] not found$config[zx-overlay] not found