Transaksjon forplantning og isolasjon på våren @ Transactional

1. Introduksjon

I denne opplæringen vil vi dekke @Transaksjonell kommentar og dens isolering og forplantning innstillinger.

2. Hva er? @Transaksjonell?

Vi kan bruke @Transaksjonell å pakke inn en metode i en databasetransaksjon.

Det lar oss sette forplantnings-, isolasjons-, timeout-, skrivebeskyttelses- og tilbakeføringsbetingelser for transaksjonen. Vi kan også spesifisere transaksjonssjefen.

2.1. @Transaksjonell Implementeringsdetaljer

Spring oppretter en proxy eller manipulerer klasse-byte-koden for å administrere opprettelse, forpliktelse og tilbakeføring av transaksjonen. I tilfelle fullmektig ignorerer Spring @Transaksjonell i interne metodesamtaler.

Enkelt sagt, hvis vi har en metode som callMethod og vi merker det som @Transaksjonell, Våren ville pakke inn noen transaksjonsstyringskoder rundt påkallelsen:@Transaksjonell metoden kalt:

createTransactionIfNecessary (); prøv {callMethod (); commitTransactionAfterReturning (); } fange (unntak) {completeTransactionAfterThrowing (); kaste unntak; }

2.2. Hvordan å bruke @Transaksjonell

Vi kan sette kommentaren på definisjoner av grensesnitt, klasser eller direkte på metoder. De overstyrer hverandre i henhold til prioritetsrekkefølgen; fra laveste til høyeste har vi: Grensesnitt, superklasse, klasse, grensesnittmetode, superklassemetode og klassemetode.

Våren bruker klassekommentar på alle offentlige metoder i denne klassen som vi ikke kommenterte @Transaksjonell .

Imidlertid, hvis vi legger kommentaren til en privat eller beskyttet metode, vil Spring ignorere den uten feil.

La oss starte med et grensesnitteksempel:

@Transactional public interface TransferService {void transfer (String user1, String user2, double val); } 

Vanligvis anbefales det ikke å stille inn @Transaksjonell på grensesnittet. Det er imidlertid akseptabelt i tilfeller som @Oppbevaringssted med Spring Data.

Vi kan sette merknaden på en klassedefinisjon for å overstyre transaksjonsinnstillingen til grensesnittet / superklassen:

@Service @Transactional public class TransferServiceImpl implementerer TransferService {@Override public void transfer (String user1, String user2, double val) {// ...}}

La oss nå overstyre det ved å sette kommentaren direkte på metoden:

@Transactional public void transfer (String user1, String user2, double val) {// ...}

3. Transaksjon forplantning

Formering definerer vår forretningslogikk 's transaksjonsgrense. Våren klarer å starte og stoppe en transaksjon i henhold til vår forplantning omgivelser.

Våren kaller TransactionManager :: getTransaction for å få eller opprette en transaksjon i henhold til forplantningen. Den støtter noen av forplantningene for alle typer TransactionManager, men det er noen få av dem som bare støttes av spesifikke implementeringer av TransactionManager.

La oss nå gå gjennom de forskjellige forplantningene og hvordan de fungerer.

3.1. PÅKREVET Formering

PÅKREVET er standard forplantning. Våren sjekker om det er en aktiv transaksjon, så oppretter den en ny hvis ingenting eksisterte. Ellers føyes virksomhetslogikken til den nåværende aktive transaksjonen:

@Transactional (propagation = Propagation.REQUIRED) public void requiredExample (String user) {// ...}

Også som PÅKREVET er standard forplantning, kan vi forenkle koden ved å slippe den:

@ Transaksjonelt offentlig tomrom kreves Eksempel (strengbruker) {// ...}

La oss se pseudokoden for hvordan transaksjonsoppretting fungerer for PÅKREVET forplantning:

if (isExistingTransaction ()) {if (isValidateExistingTransaction ()) {validateExisitingAndThrowExceptionIfNotValid (); } returner eksisterende; } returner createNewTransaction ();

3.2. STØTTER Formering

Til STØTTER, Spring sjekker først om en aktiv transaksjon eksisterer. Hvis det eksisterer en transaksjon, vil den eksisterende transaksjonen bli brukt. Hvis det ikke er en transaksjon, blir den utført ikke-transaksjonell:

@Transactional (propagation = Propagation.SUPPORTS) public void supportsExample (String user) {// ...}

La oss se pseudokoden for transaksjonsopprettelsen for STØTTER:

if (isExistingTransaction ()) {if (isValidateExistingTransaction ()) {validateExisitingAndThrowExceptionIfNotValid (); } returner eksisterende; } returner tomTransaksjon;

3.3. PÅBUDT, BINDENDE Formering

Når forplantningen er PÅBUDT, BINDENDE, hvis det er en aktiv transaksjon, vil den bli brukt. Hvis det ikke er en aktiv transaksjon, kaster Spring et unntak:

@Transactional (propagation = Propagation.MANDATORY) public void obligatoryExample (String user) {// ...}

Og la oss igjen se pseudokoden:

if (isExistingTransaction ()) {if (isValidateExistingTransaction ()) {validateExisitingAndThrowExceptionIfNotValid (); } returner eksisterende; } kast IllegalTransactionStateException;

3.4. ALDRI Formering

For transaksjonslogikk med ALDRI forplantning, Spring kaster et unntak hvis det er en aktiv transaksjon:

@Transactional (propagation = Propagation.NEVER) public void neverExample (String user) {// ...}

La oss se pseudokoden for hvordan transaksjonsoppretting fungerer for ALDRI forplantning:

if (isExistingTransaction ()) {kast IllegalTransactionStateException; } returner tomTransaksjon;

3.5. IKKE STØTTET Formering

Våren stanser først den nåværende transaksjonen hvis den eksisterer, og deretter utføres forretningslogikken uten en transaksjon.

@Transactional (propagation = Propagation.NOT_SUPPORTED) public void notSupportedExample (String user) {// ...}

De JTATransactionManager støtter ekte transaksjonssuspensjon utenom boksen. Andre simulerer suspensjonen ved å holde en referanse til den eksisterende og deretter fjerne den fra trådkonteksten

3.6. KRÆVER_NEW Formering

Når forplantningen er KRÆVER_NEW, Spring stanser gjeldende transaksjon hvis den eksisterer og oppretter en ny:

@Transactional (propagation = Propagation.REQUIRES_NEW) offentlig tomrom kreverNewExample (strengbruker) {// ...}

Lik IKKE STØTTET, vi trenger JTATransactionManager for faktisk suspensjon av transaksjonen.

Og pseudokoden ser slik ut:

hvis (isExistingTransaction ()) {suspendere (eksisterende); prøv {return createNewTransaction (); } fange (unntak) {resumeAfterBeginException (); kaste unntak; }} returner createNewTransaction ();

3.7. NESTED Formering

Til NESTED forplantning, Spring sjekker om en transaksjon eksisterer, og hvis ja, markerer den et lagringspunkt. Dette betyr at hvis vår forretningslogikkutførelse gir et unntak, så tilbakeføres transaksjoner til dette lagringspunktet. Hvis det ikke er noen aktiv transaksjon, fungerer det som PÅKREVET .

DataSourceTransactionManager støtter denne forplantningen utenom boksen. Også noen implementeringer av JTATransactionManager kan støtte dette.

JpaTransactionManager støtter NESTED bare for JDBC-tilkoblinger. Men hvis vi setter nestedTransactionAllowed flagg til ektefungerer det også for JDBC-tilgangskode i JPA-transaksjoner hvis vår JDBC-driver støtter lagringspunkter.

Til slutt, la oss stille inn forplantning til NESTED:

@Transactional (propagation = Propagation.NESTED) public void nestedExample (String user) {// ...}

4. Transaksjonsisolasjon

Isolering er en av de vanlige syreegenskapene: Atomisitet, konsistens, isolasjon og holdbarhet. Isolasjon beskriver hvordan endringer som brukes av samtidige transaksjoner er synlige for hverandre.

Hvert isolasjonsnivå forhindrer null eller flere samtidige bivirkninger på en transaksjon:

  • Skitten lesning: les den uforpliktede endringen av en samtidig transaksjon
  • Gjentatt lesing: få annen verdi ved omlesing av en rad hvis en samtidig transaksjon oppdaterer den samme raden og forplikter seg
  • Fantom leste: få forskjellige rader etter re-kjøring av en rekkeforespørsel hvis en annen transaksjon legger til eller fjerner noen rader i området og forplikter seg

Vi kan sette isolasjonsnivået for en transaksjon etter @Transactional :: isolasjon. Den har disse fem oppregningene om våren: MISLIGHOLDE, READ_UNCOMMITTED, LES_KOMMITTERT, REPEATABLE_READ, SERIALISERbar.

4.1. Isolasjonsledelse om våren

Standard isolasjonsnivå er MISLIGHOLDE. Så når Spring oppretter en ny transaksjon, vil isolasjonsnivået være standardisolasjonen av RDBMS. Derfor bør vi være forsiktige hvis vi endrer databasen.

Vi bør også vurdere tilfeller når vi kaller en kjede av metoder med ulik isolasjon. I normal flyt gjelder isolasjonen bare når en ny transaksjon ble opprettet. Så hvis vi av en eller annen grunn ikke vil tillate at en metode utføres i annen isolasjon, må vi stille TransactionManager :: setValidateExistingTransaction til sant. Da vil pseudokoden for validering av transaksjonen være:

if (isolationLevel! = ISOLATION_DEFAULT) {if (currentTransactionIsolationLevel ()! = isolationLevel) {throw IllegalTransactionStateException}}

La oss nå komme dypt i forskjellige isolasjonsnivåer og deres effekter.

4.2. READ_UNCOMMITTED Isolering

READ_UNCOMMITTED er det laveste isolasjonsnivået og gir mest mulig tilgang.

Som et resultat lider det av alle de tre nevnte samtidige bivirkningene. Så en transaksjon med denne isolasjonen leser uforpliktende data om andre samtidige transaksjoner. Dessuten kan både ikke-repeterbare og fantomlesninger skje. Dermed kan vi få et annet resultat ved omlesing av en rad eller re-kjøring av en rekkeforespørsel.

Vi kan stille inn isolering nivå for en metode eller klasse:

@Transactional (isolation = Isolation.READ_UNCOMMITTED) offentlig ugyldig logg (strengmelding) {// ...}

Postgres støtter ikke READ_UNCOMMITTED isolasjon og faller tilbake til READ_COMMITED i stedet. Oracle støtter ikke og tillater ikke READ_UNCOMMITTED.

4.3. LES_KOMMITTERT Isolering

Det andre nivået av isolasjon, LES_KOMMITTERT, forhindrer skitne avlesninger.

Resten av samtidige bivirkninger kan fortsatt skje. Så uforpliktede endringer i samtidige transaksjoner har ingen innvirkning på oss, men hvis en transaksjon begår endringene, kan resultatet vårt endres ved å spørre på nytt.

Her setter vi isolering nivå:

@Transactional (isolation = Isolation.READ_COMMITTED) offentlig ugyldighetslogg (strengmelding) {// ...}

LES_KOMMITTERT er standardnivået med Postgres, SQL Server og Oracle.

4.4. REPEATABLE_READ Isolering

Det tredje nivået av isolasjon, REPEATABLE_READ, forhindrer skitne og ikke-repeterbare lesninger. Så vi blir ikke berørt av uforpliktende endringer i samtidige transaksjoner.

Også når vi spør etter en rad, får vi ikke et annet resultat. Men i re-kjøring av rekkevidde-spørringer, kan vi få nylig lagt til eller fjernet rader.

Videre er det det laveste nivået som kreves for å forhindre den tapte oppdateringen. Den tapte oppdateringen oppstår når to eller flere samtidige transaksjoner leser og oppdaterer samme rad. REPEATABLE_READ tillater ikke i det hele tatt samtidig tilgang til en rad. Derfor kan den tapte oppdateringen ikke skje.

Slik stiller du inn isolering nivå for en metode:

@Transactional (isolation = Isolation.REPEATABLE_READ) offentlig ugyldig logg (strengmelding) {// ...}

REPEATABLE_READ er standardnivået i Mysql. Oracle støtter ikke REPEATABLE_READ.

4.5. SERIALISERbar Isolering

SERIALISERbar er det høyeste nivået av isolasjon. Det forhindrer alle nevnte samtidige bivirkninger, men kan føre til den laveste samtidige tilgangshastigheten fordi den utfører samtidige samtaler sekvensielt.

Med andre ord, samtidig utførelse av en gruppe serierbare transaksjoner har samme resultat som å utføre dem i serie.

La oss nå se hvordan du setter inn SERIALISERbar som isolering nivå:

@Transactional (isolation = Isolation.SERIALIZABLE) offentlig ugyldig logg (strengmelding) {// ...}

5. Konklusjon

I denne opplæringen undersøkte vi forplantningsegenskapene til @Transaksjon i detalj. Etterpå lærte vi om samtidige bivirkninger og isolasjonsnivåer.

Som alltid kan du finne den fullstendige koden på GitHub.