Introduktion til transaktioner i Java og forår

1. Introduktion

I denne vejledning forstår vi, hvad der menes med transaktioner i Java. Dermed vil vi forstå, hvordan man udfører lokale ressourcer og globale transaktioner. Dette giver os også mulighed for at udforske forskellige måder at håndtere transaktioner i Java og Spring på.

2. Hvad er en transaktion?

Transaktioner i Java, som generelt henviser til en række handlinger, som alle skal gennemføres med succes. Derfor, hvis en eller flere handlinger mislykkes, skal alle andre handlinger trække sig tilbage og lade applikationens tilstand være uændret. Dette er nødvendigt for at sikre, at applikationstilstandens integritet aldrig kompromitteres.

Disse transaktioner kan også involvere en eller flere ressourcer som database, meddelelseskø, hvilket giver anledning til forskellige måder at udføre handlinger under en transaktion på. Disse inkluderer udførelse af lokale ressourcetransaktioner med individuelle ressourcer. Alternativt kan flere ressourcer deltage i en global transaktion.

3. Ressource lokale transaktioner

Vi undersøger først, hvordan vi kan bruge transaktioner i Java, mens vi arbejder med individuelle ressourcer. Her har vi måske flere individuelle handlinger, som vi udfører med en ressource som en database. Men vi vil måske have dem til at ske som en samlet helhed som i en udelelig enhed af arbejde. Med andre ord ønsker vi, at disse handlinger skal ske under en enkelt transaktion.

I Java har vi flere måder at få adgang til og operere på en ressource som en database. Derfor er den måde, vi håndterer transaktioner på, heller ikke den samme. I dette afsnit finder vi ud af, hvordan vi kan bruge transaktioner med nogle af disse biblioteker i Java, som ofte bruges.

3.1. JDBC

Java Database Connectivity (JDBC) er API i Java, der definerer, hvordan man får adgang til databaser i Java. Forskellige databaseleverandører leverer JDBC-drivere til at oprette forbindelse til databasen på en leverandør-agnostisk måde. Så vi henter en Forbindelse fra en driver til at udføre forskellige operationer på databasen:

JDBC giver os mulighederne for at udføre erklæringer under en transaktion. Det standardadfærd for en Forbindelse er automatisk forpligtet. For at præcisere, hvad dette betyder er, at hver enkelt erklæring behandles som en transaktion og automatisk begås lige efter udførelsen.

Men hvis vi ønsker at samle flere udsagn i en enkelt transaktion, er det også muligt at opnå:

Forbindelsesforbindelse = 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 (); forbindelse.forpligtelse (); } fange (Undtagelse e) {connection.rollback (); }

Her har vi deaktiveret auto-commit-tilstanden af Forbindelse. Derfor kan vi manuelt definere transaktionsgrænsen og udføre en begå eller tilbageførsel. JDBC giver os også mulighed for at indstille en Savepoint det giver os mere kontrol over, hvor meget vi skal tilbageføres.

3.2. JPA

Java Persistence API (JPA) er en specifikation i Java, der kan bruges til bygge bro over kløften mellem objektorienterede domænemodeller og relationsdatabasesystemer. Så der er flere implementeringer af JPA tilgængelige fra tredjeparter som Hibernate, EclipseLink og iBatis.

I JPA kan vi definere regelmæssige klasser som en Enhed der giver dem vedvarende identitet. Det EntityManager klasse giver den nødvendige grænseflade til at arbejde med flere enheder inden for en persistens-kontekst. Persistens-konteksten kan betragtes som en cache på første niveau, hvor enheder administreres:

Vedholdenhedskonteksten her kan være af to typer, transaktionsomfanget eller udvidet omfang. En transaktion-scoped persistens-kontekst er bundet til en enkelt transaktion. Mens persistens-konteksten med udvidet rækkevidde kan spænde over flere transaktioner. Det standardområdet for en persistens-kontekst er transaktionens omfang.

Lad os se, hvordan vi kan oprette en EntityManager og definere en transaktionsgrænse 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 (); } fange (Exceotion e) {entityManager.getTransaction (). rollback (); }

Her opretter vi en EntityManager fra EntityManagerFactory inden for rammerne af en transaktionsomfanget persistens-kontekst. Så definerer vi transaktionsgrænsen med begynde, begå, og tilbageførsel metoder.

3.3. JMS

Java Messaging Service (JMS) er en specifikation i Java, der tillader applikationer at kommunikere asynkront ved hjælp af beskeder. API'en giver os mulighed for at oprette, sende, modtage og læse meddelelser fra en kø eller et emne. Der er flere messaging-tjenester, der overholder JMS-specifikationer, herunder OpenMQ og ActiveMQ.

JMS API understøtter bundtning af flere send- eller modtagefunktioner i en enkelt transaktion. Imidlertid er arten af ​​en meddelelsesbaseret integrationsarkitektur dog produktion og forbrug af en besked kan ikke være en del af den samme transaktion. Transaktionens omfang forbliver mellem klienten og JMS-udbyderen:

JMS giver os mulighed for at oprette en Session fra en Forbindelse som vi får fra en leverandørspecifik ConnectionFactory. Vi har en mulighed for at oprette en Session der behandles eller ej. For ikke-transaktion Sessions, vi kan også definere en passende godkendelsesfunktion.

Lad os se, hvordan vi kan skabe en transaktion Session at sende flere beskeder under en transaktion:

ActiveMQConnectionFactory connectionFactory = ny ActiveMQConnectionFactory (CONNECTION_URL); Forbindelsesforbindelse = = connectionFactory.createConnection (); forbindelse.start (); prøv {Session session = connection.createSession (true, 0); Destination = destination = session.createTopic ("TEST.FOO"); MessageProducer producer = session.createProducer (destination); producer.send (firstMessage); producer.send (secondMessage); session.commit (); } fange (Undtagelse e) {session.rollback (); }

Her opretter vi en MessageProducer til Bestemmelsessted af typen af ​​emne. Vi får Bestemmelsessted fra Session vi skabte tidligere. Vi bruger videre Session at definere transaktionsgrænser ved hjælp af metoderne begå og tilbageførsel.

4. Globale transaktioner

Som vi så ressource lokale transaktioner tillade os at udføre flere operationer inden for en enkelt ressource som en samlet helhed. Men ofte behandler vi det operationer, der spænder over flere ressourcer. For eksempel drift i to forskellige databaser eller en database og en meddelelseskø. Her er lokal transaktionsstøtte inden for ressourcer ikke tilstrækkelig for os.

Hvad vi har brug for i disse scenarier er en global mekanisme til at afgrænse transaktioner, der spænder over flere deltagende ressourcer. Dette kaldes ofte distribuerede transaktioner, og der er specifikationer, der er foreslået for at håndtere dem effektivt.

Det XA-specifikation er en sådan specifikation, der definerer en transaktionsadministrator til at kontrollere transaktion på tværs af flere ressourcer. Java har ret moden support til distribuerede transaktioner, der overholder XA-specifikationen gennem komponenterne JTA og JTS.

4.1. JTA

Java Transaction API (JTA) er en Java Enterprise Edition API udviklet under Java Community Process. Det gør det muligt for Java-applikationer og applikationsservere at udføre distribuerede transaktioner på tværs af XA-ressourcer. JTA er modelleret omkring XA-arkitektur og udnytter to-faset forpligtelse.

JTA specificerer standard Java-grænseflader mellem en transaktionsadministrator og de andre parter i en distribueret transaktion:

Lad os forstå nogle af de vigtigste grænseflader fremhævet ovenfor:

  • TransactionManager: En grænseflade, der gør det muligt for en applikationsserver at afgrænse og kontrollere transaktioner
  • UserTransaction: Denne grænseflade gør det muligt for et applikationsprogram at afgrænse og kontrollere transaktioner eksplicit
  • XAResource: Formålet med denne grænseflade er at tillade en transaktionsadministrator at arbejde med ressourceadministratorer for XA-kompatible ressourcer

4.2. JTS

Java Transaction Service (JTS) er en specifikation til opbygning af transaktionsadministratoren, der kortlægges til OMG OTS-specifikationen. JTS bruger standard CORBA ORB / TS-grænseflader og Internet Inter-ORB Protocol (IIOP) til formering af transaktionssammenhæng mellem JTS-transaktionsadministratorer.

På et højt niveau understøtter det Java Transaction API (JTA). En JTS-transaktionschef leverer transaktionstjenester til de parter, der er involveret i en distribueret transaktion:

Tjenester, som JTS leverer til en applikation, er stort set gennemsigtige, og derfor bemærker vi dem ikke engang i applikationsarkitekturen. JTS er arkitekteret omkring en applikationsserver, der uddrager al transaktionssemantik fra applikationsprogrammerne.

5. JTA-transaktionsstyring

Nu er det tid til at forstå, hvordan vi kan administrere en distribueret transaktion ved hjælp af JTA. Distribuerede transaktioner er ikke trivielle løsninger og har derfor også omkostningsimplikationer. I øvrigt, der er flere muligheder, som vi kan vælge for at inkludere JTA i vores applikation. Derfor skal vores valg være i lyset af den samlede applikationsarkitektur og ambitioner.

5.1. JTA i applikationsserver

Som vi har set tidligere, er JTA-arkitektur afhængig af applikationsserveren for at lette et antal transaktionsrelaterede operationer. En af de nøgletjenester, den er afhængig af, at serveren leverer, er en navngivningstjeneste via JNDI. Det er her XA-ressourcer som datakilder er bundet til og hentet fra.

Bortset fra dette har vi et valg med hensyn til, hvordan vi vil styre transaktionsgrænsen i vores ansøgning. Dette giver anledning til to typer transaktioner inden for Java-applikationsserveren:

  • Containerstyret transaktion: Som navnet antyder, her er transaktionsgrænsen indstillet af applikationsserveren. Dette forenkler udviklingen af ​​Enterprise Java Beans (EJB), da det ikke inkluderer udsagn relateret til transaktionsafgrænsning og udelukkende er afhængige af containeren til at gøre det. Dette giver dog ikke tilstrækkelig fleksibilitet til applikationen.
  • Transaktion med bønnestyret: I modsætning til den containeradministrerede transaktion i en bønnestyret transaktion EJB'er indeholder de eksplicitte udsagn til at definere transaktionsafgrænsningen. Dette giver nøjagtig kontrol med applikationen til at markere transaktionens grænser, omend på bekostning af mere kompleksitet.

En af de største ulemper ved at udføre transaktioner i forbindelse med en applikationsserver er, at applikationen bliver tæt forbundet med serveren. Dette har implikationer med hensyn til applikationens testbarhed, håndterbarhed og bærbarhed. Dette er mere dybtgående inden for mikroservicearkitektur, hvor vægten er mere på at udvikle serverneutrale applikationer.

5.2. JTA Standalone

De problemer, vi diskuterede i det sidste afsnit, har givet et enormt momentum i retning af at skabe løsninger til distribuerede transaktioner, der ikke er afhængige af en applikationsserver. Der er flere muligheder tilgængelige for os i denne henseende, som at bruge transaktionsunderstøttelse med Spring eller bruge en transaktionsadministrator som Atomikos.

Lad os se, hvordan vi kan bruge en transaktionsadministrator som Atomikos til at lette en distribueret transaktion med en database og en meddelelseskø. Et af nøgleaspekterne ved en distribueret transaktion er tilmelding og afnotering af de deltagende ressourcer med transaktionsovervågningen. Atomikos tager sig af dette for os. Alt vi skal gøre er at bruge abstraktioner, der leveres af Atomikos:

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

Her opretter vi en forekomst af AtomikosDataSourceBean og registrering af den leverandørspecifikke XADataSource. Herfra kan vi fortsætte med at bruge dette som enhver anden Datakilde og få fordelene ved distribuerede transaktioner.

På samme måde har vi har en abstraktion til meddelelseskø som sørger for automatisk registrering af den leverandørspecifikke XA-ressource i transaktionsovervågningen:

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

Her opretter vi en forekomst af AtomikosConnectionFactoryBean og registrering af XAConnectionFactory fra en XA-aktiveret JMS-leverandør. Herefter kan vi fortsætte med at bruge dette som en almindelig ConnectionFactory.

Nu giver Atomikos os det sidste stykke af puslespillet til at bringe alt sammen, en forekomst af UserTransaction:

UserTransaction userTransaction = ny UserTransactionImp ();

Nu er vi klar til at oprette en applikation med distribueret transaktion, der spænder over vores database og meddelelseskø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 (); Sessionssession = mbConnection.createSession (sand, 0); Destinationsdestination = session.createTopic ("TEST.FOO"); MessageProducer producer = session.createProducer (destination); producer.send (MEDDELELSE); userTransaction.commit (); } fange (Undtagelse e) {userTransaction.rollback (); }

Her bruger vi metoder begynde og begå i klassen UserTransaction for at afgrænse transaktionsgrænsen. Dette inkluderer at gemme en post i databasen samt offentliggøre en besked i meddelelseskøen.

6. Transaktionsstøtte om foråret

Vi har set det håndtering af transaktioner er snarere en involveret opgave, der inkluderer en masse kedelpladekodning og konfigurationer. Desuden har hver ressource sin egen måde at håndtere lokale transaktioner på. I Java abstraherer JTA os fra disse variationer, men bringer yderligere udbydersspecifikke detaljer og kompleksiteten af ​​applikationsserveren.

Fjederplatform giver os en meget renere måde at håndtere transaktioner på, både lokale ressourcer og globale transaktioner i Java. Dette sammen med de andre fordele ved Spring skaber en overbevisende sag for brug af Spring til at håndtere transaktioner. Desuden er det ret nemt at konfigurere og skifte en transaktionsadministrator med Spring, som kan leveres af serveren eller standalone.

Foråret giver os dette problemfri abstraktion ved at oprette en proxy til metoderne med transaktionskode. Proxyen administrerer transaktionstilstanden på vegne af koden ved hjælp af TransactionManager:

Den centrale grænseflade her er PlatformTransactionManager som har en række forskellige implementeringer til rådighed. Det giver abstraktioner over JDBC (DataSource), JMS, JPA, JTA og mange andre ressourcer.

6.1. Konfigurationer

Lad os se, hvordan vi kan konfigurere Forår at bruge Atomikos som transaktionsmanager og yde transaktionsstøtte til JPA og JMS. Vi begynder med at definere en PlatformTransactionManager af typen JTA:

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

Her giver vi forekomster af UserTransaction og TransactionManager til JTATransactionManager. Disse forekomster leveres af et transaktionsadministratorbibliotek som Atomikos:

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

Klasserne UserTransactionImp og UserTransactionManager leveres af Atomikos her.

Desuden er vi nødt til at definere JmsTemplete som kerneklassen tillader synkron JMS-adgang om foråret:

@Bean offentlig JmsTemplate jmsTemplate () kaster Throwable {returner ny JmsTemplate (connectionFactory ()); }

Her, ConnectionFactory leveres af Atomikos, hvor det muliggør distribueret transaktion for Forbindelse leveret af 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 (aktivMQXAConnectionFactory); returnere atomikosConnectionFactoryBean; }

Så som vi kan se, indpakker vi en JMS-udbyder-specifik XAConnectionFactory med AtomikosConnectionFactoryBean.

Dernæst skal vi definere en AbstractEntityManagerFactoryBean der er ansvarlig for oprettelse af JPA EntityManagerFactory bønne om foråret:

@Bean offentlig LocalContainerEntityManagerFactoryBean entityManager () kaster SQLException {LocalContainerEntityManagerFactoryBean entityManager = ny LocalContainerEntityManagerFactoryBean (); entityManager.setDataSource (dataSource ()); Egenskaber egenskaber = nye egenskaber (); egenskaber.setProperty ("javax.persistence.transactionType", "jta"); entityManager.setJpaProperties (egenskaber); returnere enhedManager; }

Som før, Datakilde som vi indstiller i LocalContainerEntityManagerFactoryBean her leveres af Atomikos med distribuerede transaktioner aktiveret:

@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 indpakker vi den udbyderspecifikke XADataSource i AtomikosDataSourceBean.

6.2. Transaktionsstyring

Efter at have gennemgået alle konfigurationerne i det sidste afsnit, skal vi føle os ganske overvældede! Vi kan endda stille spørgsmålstegn ved fordelene ved at bruge Spring. Men husk, at al denne konfiguration har muliggjorde os abstraktion fra det meste af den udbyderspecifikke kedelplade og vores aktuelle applikationskode behøver slet ikke at være opmærksom på det.

Så nu er vi klar til at undersøge, hvordan vi bruger transaktioner i foråret, hvor vi har til hensigt at opdatere databasen og udgive meddelelser. Foråret giver os to måder at opnå dette med deres egne fordele at vælge imellem. Lad os forstå, hvordan vi kan gøre brug af dem:

  • Deklarativ support

Den nemmeste måde at bruge transaktioner i foråret på er med deklarativ support. Her har vi det en bekvemhedsbemærkning, der er tilgængelig til anvendelse på metoden eller endda i klassen. Dette muliggør simpelthen global transaktion for vores kode:

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

Den enkle kode ovenfor er tilstrækkelig til at tillade en gemningsoperation i databasen og en offentliggørelse i meddelelseskø inden for en JTA-transaktion.

  • Programmatisk support

Mens den deklarative støtte er ret elegant og enkel, tilbyder den os ikke fordel ved at kontrollere transaktionsgrænsen mere præcist. Derfor, hvis vi har et bestemt behov for at opnå det, tilbyder Spring programmatisk support til at afgrænse transaktionsgrænsen:

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

Så som vi kan se, er vi nødt til at oprette en Transaktionsskabelon med det tilgængelige PlatformTransactionManager. Så kan vi bruge Transaktionstemplet at behandle en masse udsagn inden for en global transaktion.

7. Eftertanke

Som vi har set, er håndtering af transaktioner, især dem, der spænder over flere ressourcer, komplekse. I øvrigt, transaktioner er i sagens natur blokeret, hvilket er skadeligt for latenstid og gennemstrømning af en ansøgning. Yderligere er det ikke let at teste og vedligeholde kode med distribuerede transaktioner, især hvis transaktionen afhænger af den underliggende applikationsserver. Så alt i alt er det bedst at undgå transaktioner overhovedet, hvis vi kan!

Men det er langt fra virkeligheden. Kort sagt, i virkelige applikationer har vi ofte et legitimt behov for transaktioner. Selvom det er muligt at genoverveje applikationsarkitekturen uden transaktioner, det er måske ikke altid muligt. Derfor skal vi vedtage visse bedste fremgangsmåder, når vi arbejder med transaktioner i Java for at gøre vores applikationer bedre:

  • En af de grundlæggende skift, vi skal tage, er at Brug enkeltstående transaktionsadministratorer i stedet for dem, der leveres af en applikationsserver. Dette alene kan forenkle vores ansøgning meget. Desuden er det meget velegnet til cloud-native mikroservicearkitektur.
  • Yderligere, et abstraktionslag som Spring kan hjælpe os med at begrænse den direkte indvirkning fra udbydere som JPA- eller JTA-udbydere. Så dette kan gøre det muligt for os at skifte mellem udbydere uden stor indflydelse på vores forretningslogik. Desuden fjerner det os det lave ansvar for at styre transaktionstilstanden.
  • Endelig skal vi være det omhyggelig med at vælge transaktionsgrænsen i vores kode. Da transaktioner blokerer, er det altid bedre at holde transaktionsgrænsen så begrænset som muligt. Hvis det er nødvendigt, foretrækker vi programmatisk frem for deklarativ kontrol med transaktioner.

8. Konklusion

For at opsummere diskuterede vi i denne vejledning transaktioner i forbindelse med Java. Vi gennemgik support til individuelle ressource lokale transaktioner i Java til forskellige ressourcer. Vi gennemgik også måderne til at opnå globale transaktioner i Java.

Desuden gennemgik vi forskellige måder at styre globale transaktioner i Java. Vi forstod også, hvordan Spring gør det lettere for os at bruge transaktioner i Java.

Endelig gennemgik vi nogle af de bedste fremgangsmåder, når vi arbejder med transaktioner i Java.


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