Transaktionsformering og isolering om foråret @ Transactional

1. Introduktion

I denne vejledning dækker vi @Transaktionel kommentar og dens isolation og formering indstillinger.

2. Hvad er? @Transaktionel?

Vi kan bruge @Transaktionel at indpakke en metode i en databasetransaktion.

Det giver os mulighed for at indstille formerings-, isolations-, timeout-, skrivebeskyttelses- og tilbageførselsbetingelser for vores transaktion. Vi kan også specificere transaktionsadministratoren.

2.1. @Transaktionel Implementeringsoplysninger

Spring opretter en proxy eller manipulerer klasse-byte-koden til at styre oprettelse, begåelse og tilbageførsel af transaktionen. I tilfælde af en proxy ignorerer Spring @Transaktionel i interne metodeopkald.

Kort sagt, hvis vi har en metode som callMethod og vi markerer det som @Transaktionel, Foråret ville pakke nogle transaktionsstyringskoder omkring påkaldelsen:@Transaktionel metode kaldet:

createTransactionIfNecessary (); prøv {callMethod (); commitTransactionAfterReturning (); } fange (undtagelse) {completeTransactionAfterThrowing (); kast undtagelse }

2.2. Sådan bruges @Transaktionel

Vi kan sætte kommentaren på definitioner af grænseflader, klasser eller direkte på metoder. De tilsidesætter hinanden i henhold til prioritetsrækkefølgen; fra laveste til højeste har vi: Interface, superklasse, klasse, grænseflademetode, superklassemetode og klassemetode.

Foråret anvender klassens annotering på alle offentlige metoder i denne klasse, som vi ikke kommenterede @Transaktionel .

Men hvis vi sætter kommentaren på en privat eller beskyttet metode, ignorerer Spring den uden en fejl.

Lad os starte med en interfaceeksempel:

@Transactional public interface TransferService {ugyldig overførsel (streng bruger1, streng bruger2, dobbelt val); } 

Normalt anbefales det ikke at indstille @Transaktionel på grænsefladen. Det er dog acceptabelt i tilfælde som @Repository med Spring Data.

Vi kan sætte kommentaren på en klassedefinition for at tilsidesætte transaktionsindstillingen for grænsefladen / superklassen:

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

Lad os nu tilsidesætte det ved at indstille kommentaren direkte på metoden:

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

3. Forplantning af transaktion

Formering definerer vores forretningslogik 's transaktionsgrænse. Spring formår at starte og pause en transaktion i henhold til vores formering indstilling.

Forårskald TransactionManager :: getTransaction at få eller oprette en transaktion i henhold til forplantningen. Det understøtter nogle af formeringer for alle typer TransactionManager, men der er et par af dem, der kun understøttes af specifikke implementeringer af TransactionManager.

Lad os nu gennemgå de forskellige formeringer, og hvordan de fungerer.

3.1. PÅKRÆVET Formering

PÅKRÆVET er standardformering. Spring kontrollerer, om der er en aktiv transaktion, så opretter den en ny, hvis der ikke var noget. Ellers føjes forretningslogikken til den aktuelt aktive transaktion:

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

Også som PÅKRÆVET er standardformering, kan vi forenkle koden ved at slippe den:

@Transactional public void requiredExample (String user) {// ...}

Lad os se pseudokoden for, hvordan oprettelse af transaktioner fungerer for PÅKRÆVET formering:

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

3.2. BAKKER OP Formering

Til BAKKER OP, Spring kontrollerer først, om der findes en aktiv transaktion. Hvis der findes en transaktion, vil den eksisterende transaktion blive brugt. Hvis der ikke er en transaktion, udføres den ikke-transaktionel:

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

Lad os se transaktionens oprettelse pseudokode for BAKKER OP:

if (isExistingTransaction ()) {if (isValidateExistingTransaction ()) {validateExisitingAndThrowExceptionIfNotValid (); } returnere eksisterende } returner tomTransaktion;

3.3. OBLIGATORISK Formering

Når udbredelsen er OBLIGATORISK, hvis der er en aktiv transaktion, vil den blive brugt. Hvis der ikke er en aktiv transaktion, kaster Spring en undtagelse:

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

Og lad os igen se pseudokoden:

if (isExistingTransaction ()) {if (isValidateExistingTransaction ()) {validateExisitingAndThrowExceptionIfNotValid (); } returnere eksisterende } smid IllegalTransactionStateException;

3.4. ALDRIG Formering

For transaktionslogik med ALDRIG formering, Spring kaster en undtagelse, hvis der er en aktiv transaktion:

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

Lad os se pseudokoden for, hvordan oprettelse af transaktioner fungerer for ALDRIG formering:

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

3.5. IKKE UNDERSTØTTET Formering

Spring suspenderer først den aktuelle transaktion, hvis den findes, så udføres forretningslogikken uden en transaktion.

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

Det JTATransactionManager understøtter ægte transaktionssuspension uden for kassen. Andre simulerer suspensionen ved at holde en henvisning til den eksisterende og derefter rydde den fra trådkonteksten

3.6. KRÆVER_NEW Formering

Når udbredelsen er KRÆVER_NEW, Spring suspenderer den aktuelle transaktion, hvis den eksisterer, og opretter derefter en ny:

@Transactional (propagation = Propagation.REQUIRES_NEW) offentlig tomrum kræverNewExample (strengbruger) {// ...}

Svarende til IKKE UNDERSTØTTET, vi har brug for JTATransactionManager for faktisk transaktionssuspension.

Og pseudokoden ser sådan ud:

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

3.7. NESTED Formering

Til NESTED forplantning, Spring kontrollerer, om der findes en transaktion, og hvis ja, markerer den et gemingspunkt. Dette betyder, at hvis vores forretningslogik udfører en undtagelse, så tilbageføres transaktioner til dette savepoint. Hvis der ikke er nogen aktiv transaktion, fungerer den som PÅKRÆVET .

DataSourceTransactionManager understøtter denne formering uden for kassen. Også nogle implementeringer af JTATransactionManager kan støtte dette.

JpaTransactionManager bakker op NESTED kun til JDBC-forbindelser. Men hvis vi sætter nestedTransactionAllowed flag til rigtigt, det fungerer også for JDBC-adgangskode i JPA-transaktioner, hvis vores JDBC-driver understøtter savepoints.

Lad os endelig indstille formering til NESTED:

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

4. Transaktionsisolering

Isolering er en af ​​de almindelige syreegenskaber: Atomicitet, konsistens, isolering og holdbarhed. Isolation beskriver, hvordan ændringer anvendt af samtidige transaktioner er synlige for hinanden.

Hvert isolationsniveau forhindrer nul eller flere samtidige bivirkninger på en transaktion:

  • Beskidt læsning: læse den uforpligtede ændring af en samtidig transaktion
  • Ikke-gentagelig læsning: få anden værdi ved genlæsning af en række, hvis en samtidig transaktion opdaterer den samme række og forpligter sig
  • Phantom læste: få forskellige rækker efter genudførelse af en rækkeforespørgsel, hvis en anden transaktion tilføjer eller fjerner nogle rækker i området og forpligter sig

Vi kan indstille isolationsniveauet for en transaktion efter @Transactional :: isolation. Det har disse fem optællinger i foråret: STANDARD, READ_UNCOMMITTED, LÆS_KOMMITTERET, REPEATABLE_READ, SERIALISERBART.

4.1. Isolationsstyring om foråret

Standardisoleringsniveauet er STANDARD. Så når Spring opretter en ny transaktion, vil isolationsniveauet være standardisolationen af ​​vores RDBMS. Derfor skal vi være forsigtige, hvis vi ændrer databasen.

Vi bør også overveje tilfælde, hvor vi kalder en kæde af metoder med forskellig isolering. I den normale strømning gælder isolationen kun, når en ny transaktion oprettes. Så hvis vi af en eller anden grund ikke vil tillade, at en metode udføres i forskellige isolationer, er vi nødt til at indstille TransactionManager :: setValidateExistingTransaction til sandt. Derefter vil pseudokoden for validering af transaktion være:

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

Lad os nu komme dybt i forskellige isolationsniveauer og deres virkninger.

4.2. READ_UNCOMMITTED Isolation

READ_UNCOMMITTED er det laveste isolationsniveau og giver mulighed for mest samtidig adgang.

Som et resultat lider det af alle tre nævnte samtidige bivirkninger. Så en transaktion med denne isolation læser ikke-forpligtede data om andre samtidige transaktioner. Der kan også ske både ikke-gentagelige og fantomlæsninger. Således kan vi få et andet resultat ved genlæsning af en række eller genudførelse af en rækkeforespørgsel.

Vi kan indstille isolation niveau for en metode eller klasse:

@Transactional (isolation = Isolation.READ_UNCOMMITTED) offentlig tomrumslog (strengbesked) {// ...}

Postgres understøtter ikke READ_UNCOMMITTED isolation og falder tilbage til READ_COMMITED i stedet. Oracle understøtter og tillader heller ikke READ_UNCOMMITTED.

4.3. LÆS_COMMITTERET Isolation

Det andet niveau af isolation, LÆS_COMMITTERET, forhindrer beskidte læser.

Resten af ​​samtidige bivirkninger kunne stadig ske. Så ikke-forpligtede ændringer i samtidige transaktioner har ingen indvirkning på os, men hvis en transaktion forpligter sine ændringer, kan vores resultat ændre sig ved at stille en forespørgsel.

Her indstiller vi isolation niveau:

@Transactional (isolation = Isolation.READ_COMMITTED) offentlig ugyldig log (strengbesked) {// ...}

LÆS_KOMMITTERET er standardniveauet med Postgres, SQL Server og Oracle.

4.4. REPEATABLE_READ Isolation

Det tredje niveau af isolation, REPEATABLE_READ, forhindrer beskidte og ikke-gentagelige læsninger. Så vi påvirkes ikke af uforpligtede ændringer i samtidige transaktioner.

Når vi forespørger om en række, får vi heller ikke et andet resultat. Men i genudførelsen af ​​rækkevidde-forespørgsler kan vi få nyligt tilføjede eller fjernede rækker.

Desuden er det det lavest krævede niveau for at forhindre den mistede opdatering. Den mistede opdatering opstår, når to eller flere samtidige transaktioner læser og opdaterer den samme række. REPEATABLE_READ tillader slet ikke samtidig adgang til en række. Derfor kan den mistede opdatering ikke ske.

Sådan indstilles isolation niveau for en metode:

@Transactional (isolation = Isolation.REPEATABLE_READ) offentlig tomrumslog (strengbesked) {// ...}

REPEATABLE_READ er standardniveauet i Mysql. Oracle understøtter ikke REPEATABLE_READ.

4.5. SERIALISERBART Isolation

SERIALISERBART er det højeste niveau af isolation. Det forhindrer alle nævnte samtidige bivirkninger, men kan føre til den laveste samtidige adgangshastighed, fordi den udfører samtidige opkald sekventielt.

Med andre ord har samtidig udførelse af en gruppe af seriøse transaktioner det samme resultat som at udføre dem i serie.

Lad os nu se, hvordan du indstiller SERIALISERBART som den isolation niveau:

@Transactional (isolation = Isolation.SERIALIZABLE) offentlig ugyldig log (streng besked) {// ...}

5. Konklusion

I denne vejledning undersøgte vi udbredelsesegenskaberne for @Transaktion i detaljer. Derefter lærte vi om samtidige bivirkninger og isolationsniveauer.

Som altid kan du finde den komplette kode på GitHub.