Voortplanting en isolatie van transacties in het voorjaar @Transactional

1. Inleiding

In deze tutorial behandelen we de @Transactional annotatie en zijn isolatie en voortplanting instellingen.

2. Wat is @Transactional?

We kunnen gebruiken @Transactional om een ​​methode in een databasetransactie te verpakken.

Het stelt ons in staat om voorwaarden voor voortplanting, isolatie, time-out, alleen-lezen en terugdraaien voor onze transactie in te stellen. Ook kunnen we de transactiemanager specificeren.

2.1. @Transactional Implementatiedetails

Spring creëert een proxy of manipuleert de byte-code van de klasse om het maken, vastleggen en terugdraaien van de transactie te beheren. In het geval van een proxy negeert Spring @Transactional in interne methodeaanroepen.

Simpel gezegd, als we een methode hebben zoals callMethod en we markeren het als @Transactional, Spring zou een transactiebeheercode rond de aanroep wikkelen:@Transactional methode genaamd:

createTransactionIfNnodig (); probeer {callMethod (); commitTransactionAfterReturning (); } catch (uitzondering) {completeTransactionAfterThrowing (); gooi uitzondering; }

2.2. Hoe te gebruiken @Transactional

We kunnen de annotatie op definities van interfaces, klassen of direct op methoden plaatsen. Ze overschrijven elkaar volgens de prioriteitsvolgorde; van laag naar hoog hebben we: interface, superklasse, klasse, interfacemethode, superklasse-methode en klassemethode.

Spring past de annotatie op klassenniveau toe op alle openbare methoden van deze klasse waarmee we niet hebben geannoteerd @Transactional .

Als we de annotatie echter op een privé- of beschermde methode plaatsen, zal Spring deze foutloos negeren.

Laten we beginnen met een voorbeeld van een interface:

@Transactional openbare interface TransferService {ongeldige overdracht (String user1, String user2, double val); } 

Meestal wordt het niet aanbevolen om de @Transactional op de interface. Het is echter acceptabel voor gevallen zoals @Repository met Spring Data.

We kunnen de annotatie op een klassedefinitie plaatsen om de transactie-instelling van de interface / superklasse te overschrijven:

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

Laten we het nu overschrijven door de annotatie rechtstreeks op de methode in te stellen:

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

3. Voortplanting van transacties

Voortplanting bepaalt de transactiegrens van onze bedrijfslogica. Spring slaagt er volgens ons in om een ​​transactie te starten en te pauzeren voortplanting instelling.

De lente roept TransactionManager :: getTransaction om een ​​transactie op te halen of te creëren volgens de propagatie. Het ondersteunt enkele van de voortplantingen voor alle soorten TransactionManager, maar er zijn er een paar die alleen worden ondersteund door specifieke implementaties van TransactionManager.

Laten we nu eens kijken naar de verschillende voortplantingen en hoe ze werken.

3.1. VERPLICHT Voortplanting

VERPLICHT is de standaard propagatie. Spring controleert of er een actieve transactie is en maakt een nieuwe aan als er niets bestond. Anders voegt de bedrijfslogica toe aan de momenteel actieve transactie:

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

Ook als VERPLICHT is de standaardpropagatie, we kunnen de code vereenvoudigen door deze te verwijderen:

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

Laten we eens kijken naar de pseudo-code van hoe het maken van transacties werkt VERPLICHT voortplanting:

if (isExistingTransaction ()) {if (isValidateExistingTransaction ()) {validateExisitingAndThrowExceptionIfNotValid (); } retourneer bestaande; } return createNewTransaction ();

3.2. ONDERSTEUNT Voortplanting

Voor ONDERSTEUNTSpringt Spring eerst na of er een actieve transactie bestaat. Als er een transactie bestaat, wordt de bestaande transactie gebruikt. Als er geen transactie is, wordt deze niet-transactionele uitgevoerd:

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

Laten we eens kijken naar de pseudo-code van het maken van de transactie voor ONDERSTEUNT:

if (isExistingTransaction ()) {if (isValidateExistingTransaction ()) {validateExisitingAndThrowExceptionIfNotValid (); } retourneer bestaande; } return emptyTransaction;

3.3. VERPLICHT Voortplanting

Wanneer de voortplanting is VERPLICHT, als er een actieve transactie is, wordt deze gebruikt. Als er geen actieve transactie is, genereert Spring een uitzondering:

@Transactional (propagation = Propagation.MANDATORY) public void verplichtExample (String-gebruiker) {// ...}

En laten we opnieuw de pseudo-code bekijken:

if (isExistingTransaction ()) {if (isValidateExistingTransaction ()) {validateExisitingAndThrowExceptionIfNotValid (); } retourneer bestaande; } gooi IllegalTransactionStateException;

3.4. NOOIT Voortplanting

Voor transactionele logica met NOOIT voortplanting genereert Spring een uitzondering als er een actieve transactie is:

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

Laten we eens kijken naar de pseudo-code van hoe het maken van transacties werkt NOOIT voortplanting:

if (isExistingTransaction ()) {throw IllegalTransactionStateException; } return emptyTransaction;

3.5. NIET ONDERSTEUND Voortplanting

Spring schort eerst de huidige transactie op als deze bestaat, waarna de bedrijfslogica zonder transactie wordt uitgevoerd.

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

De JTATransactionManager ondersteunt out-of-the-box echte opschorting van transacties. Anderen simuleren de opschorting door een verwijzing naar de bestaande vast te houden en deze vervolgens uit de threadcontext te verwijderen

3.6. VEREIST_NEW Voortplanting

Wanneer de voortplanting is VEREIST_NEW, Spring schort de huidige transactie op als deze bestaat en maakt vervolgens een nieuwe aan:

@Transactional (propagation = Propagation.REQUIRES_NEW) public void vereistNewExample (String-gebruiker) {// ...}

Gelijkwaardig aan NIET ONDERSTEUNDhebben we de JTATransactionManager voor daadwerkelijke opschorting van de transactie.

En de pseudo-code ziet er zo uit:

if (isExistingTransaction ()) {opschorten (bestaand); probeer {return createNewTransaction (); } catch (uitzondering) {resumeAfterBeginException (); gooi uitzondering; }} return createNewTransaction ();

3.7. GENEST Voortplanting

Voor GENEST propagation, Spring controleert of er een transactie bestaat, en zo ja, dan markeert het een opslagpunt. Dit betekent dat als de uitvoering van onze bedrijfslogica een uitzondering genereert, de transactie wordt teruggedraaid naar dit opslagpunt. Als er geen actieve transactie is, werkt het als VERPLICHT .

DataSourceTransactionManager ondersteunt deze voortplanting out-of-the-box. Ook zijn enkele implementaties van JTATransactionManager kan dit ondersteunen.

JpaTransactionManager ondersteunt GENEST alleen voor JDBC-verbindingen. Als we echter instellen nestedTransactionAllowed vlag naar waar, het werkt ook voor JDBC-toegangscode in JPA-transacties als onze JDBC-driver opslagpunten ondersteunt.

Laten we tot slot de voortplanting naar GENEST:

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

4. Transactie-isolatie

Isolatie is een van de algemene ACID-eigenschappen: atomiciteit, consistentie, isolatie en duurzaamheid. Isolatie beschrijft hoe wijzigingen die door gelijktijdige transacties worden aangebracht, voor elkaar zichtbaar zijn.

Elk isolatieniveau voorkomt nul of meer gelijktijdige bijwerkingen op een transactie:

  • Vies gelezen: lees de niet-gecommitteerde wijziging van een gelijktijdige transactie
  • Niet-herhaalbaar lezen: krijg een andere waarde bij het opnieuw lezen van een rij als een gelijktijdige transactie dezelfde rij bijwerkt en vastlegt
  • Phantom lezen: verkrijg verschillende rijen na het opnieuw uitvoeren van een bereikquery als een andere transactie enkele rijen toevoegt of verwijdert in het bereik en vastlegt

We kunnen het isolatieniveau van een transactie instellen door @Transactional :: isolatie. Het heeft deze vijf opsommingen in het voorjaar: STANDAARD, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALISEERBAAR.

4.1. Isolatiemanagement in het voorjaar

Het standaard isolatieniveau is STANDAARD. Dus wanneer Spring een nieuwe transactie maakt, is het isolatieniveau de standaardisolatie van ons RDBMS. Daarom moeten we voorzichtig zijn als we de database wijzigen.

We moeten ook gevallen overwegen waarin we een keten van methoden met verschillende isolatie noemen. In de normale stroom is de isolatie alleen van toepassing wanneer er een nieuwe transactie wordt gemaakt. Dus als we om welke reden dan ook niet willen dat een methode in een ander isolement wordt uitgevoerd, moeten we instellen TransactionManager :: setValidateExistingTransaction naar waar. Dan is de pseudo-code van transactievalidatie:

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

Laten we nu eens kijken naar verschillende isolatieniveaus en hun effecten.

4.2. READ_UNCOMMITTED Isolatie

READ_UNCOMMITTED is het laagste isolatieniveau en biedt de meeste gelijktijdige toegang.

Als gevolg hiervan lijdt het aan alle drie genoemde bijwerkingen van gelijktijdigheid. Dus een transactie met deze isolatie leest niet-gecommitteerde gegevens van andere gelijktijdige transacties. Ook kunnen zowel niet-herhaalbare als fantoomlezingen plaatsvinden. We kunnen dus een ander resultaat krijgen bij het opnieuw lezen van een rij of het opnieuw uitvoeren van een bereikquery.

We kunnen de isolatie niveau voor een methode of klasse:

@Transactional (isolation = Isolation.READ_UNCOMMITTED) openbaar ongeldig logboek (String-bericht) {// ...}

Postgres ondersteunt geen READ_UNCOMMITTED isolatie en valt terug op READ_COMMITED in plaats daarvan. Oracle ondersteunt en staat ook niet toe READ_UNCOMMITTED.

4.3. READ_COMMITTED Isolatie

Het tweede niveau van isolatie, READ_COMMITTED, voorkomt vuile leest.

De rest van de bijwerkingen van gelijktijdigheid kunnen nog steeds optreden. Dus niet-gecommitteerde wijzigingen in gelijktijdige transacties hebben geen invloed op ons, maar als een transactie zijn wijzigingen vastlegt, kan ons resultaat veranderen door opnieuw te vragen.

Hier stellen we de isolatie niveau:

@Transactional (isolation = Isolation.READ_COMMITTED) openbaar ongeldig logboek (String-bericht) {// ...}

READ_COMMITTED is het standaardniveau met Postgres, SQL Server en Oracle.

4.4. REPEATABLE_READ Isolatie

Het derde niveau van isolatie, REPEATABLE_READ, voorkomt vuile en niet-herhaalbare leesbewerkingen. We worden dus niet beïnvloed door niet-gecommitteerde wijzigingen in gelijktijdige transacties.

Als we een rij opnieuw opvragen, krijgen we geen ander resultaat. Maar bij het opnieuw uitvoeren van bereikquery's kunnen we nieuw toegevoegde of verwijderde rijen krijgen.

Bovendien is dit het laagste vereiste niveau om de verloren update te voorkomen. De verloren update treedt op wanneer twee of meer gelijktijdige transacties dezelfde rij lezen en bijwerken. REPEATABLE_READ staat helemaal geen gelijktijdige toegang tot een rij toe. Daarom kan de verloren update niet plaatsvinden.

Hier is hoe u het isolatie niveau voor een methode:

@Transactional (isolation = Isolation.REPEATABLE_READ) openbaar ongeldig logboek (String-bericht) {// ...}

REPEATABLE_READ is het standaardniveau in Mysql. Oracle ondersteunt niet REPEATABLE_READ.

4.5. SERIALISEERBAAR Isolatie

SERIALISEERBAAR is het hoogste niveau van isolatie. Het voorkomt alle genoemde bijwerkingen van gelijktijdigheid, maar kan leiden tot de laagste gelijktijdige toegangssnelheid omdat het gelijktijdige oproepen opeenvolgend uitvoert.

Met andere woorden, gelijktijdige uitvoering van een groep van serialiseerbare transacties heeft hetzelfde resultaat als uitvoering in serie.

Laten we nu eens kijken hoe we moeten instellen SERIALISEERBAAR als de isolatie niveau:

@Transactional (isolation = Isolation.SERIALIZABLE) openbaar ongeldig logboek (tekenreeksbericht) {// ...}

5. Conclusie

In deze zelfstudie hebben we de propagatie-eigenschap van @Transactie in detail. Daarna leerden we over bijwerkingen van gelijktijdigheid en isolatieniveaus.

Zoals altijd kun je de volledige code vinden op GitHub.