Programové riadenie transakcií na jar

1. Prehľad

Jarné @ Transakčné anotácia poskytuje pekné deklaratívne API na označenie transakčných hraníc.

V zákulisí sa aspekt stará o vytváranie a udržiavanie transakcií tak, ako sú definované pri každom výskyte súboru @ Transakčné anotácia. Tento prístup uľahčuje oddelenie našej hlavnej obchodnej logiky od prierezových problémov, ako je správa transakcií.

V tejto príručke uvidíme, že to nie je vždy najlepší prístup. Preskúmame, aké programové alternatívy poskytuje jar TransactionTemplatea naše dôvody ich použitia.

2. Problémy v raji

Predpokladajme, že zmiešame dva rôzne typy I / O v jednoduchej službe:

@Transactional public void initialPayment (PaymentRequest request) {savePaymentRequest (request); // DB callThePaymentProviderApi (požiadavka); // API updatePaymentState (požiadavka); // DB saveHistoryForAuditing (požiadavka); // DB}

Tu máme niekoľko hovorov do databázy spolu s pravdepodobne nákladným volaním rozhrania REST API. Na prvý pohľad by mohlo mať zmysel urobiť celú metódu transakčnou, pretože jednu by sme možno chceli použiť EntityManager celú operáciu vykonať atómovo.

Ak však odpoveď na toto externé rozhranie API trvá dlhšie ako obvykle, z akýchkoľvek dôvodov nám môže čoskoro dôjsť pripojenie k databáze!

2.1. Drsná podstata reality

Tu je to, čo sa stane, keď zavoláme počiatočná platba metóda:

  1. Transakčný aspekt vytvára nový EntityManager a začne novú transakciu - jednu si teda požičia Pripojenie z pripojovacieho bazéna
  2. Po prvom volaní do databázy zavolá externé API, pričom si ponechá vypožičané Pripojenie
  3. Nakoniec to využije Pripojenie vykonať zostávajúce volania databázy

Ak volanie API reaguje na chvíľu veľmi pomaly, táto metóda by vypožičala výpožičku Pripojenie pri čakaní na odpoveď.

Predstavte si, že počas tohto obdobia dostaneme dávku hovorov na server počiatočná platba metóda. Potom všetko Pripojenia môže čakať na odpoveď z volania API. To je dôvod, prečo by sa nám mohli vyčerpať databázové pripojenia - kvôli pomalej koncovej službe!

Miešanie I / O databázy s inými typmi I / O v transakčnom kontexte je nepríjemný zápach. Prvým riešením pre tieto druhy problémov je teda úplné oddelenie týchto typov I / O. Ak ich z nejakého dôvodu nemôžeme oddeliť, stále môžeme pomocou rozhrania Spring API spravovať transakcie manuálne.

3. Používanie TransactionTemplate

TransactionTemplate poskytuje množinu rozhraní API založených na spätnom volaní na manuálnu správu transakcií. Najskôr by sme ho mali inicializovať pomocou a PlatformTransactionManager.

Napríklad môžeme nastaviť túto šablónu pomocou vkladania závislostí:

// test anotácií triedy ManualTransactionIntegrationTest {@Autowired private PlatformTransactionManager transactionManager; súkromná TransactionTemplate transactionTemplate; @BeforeEach void setUp () {transactionTemplate = nový TransactionTemplate (transactionManager); } // vynechané}

The PlatformTransactionManager pomáha šablóne vytvárať, potvrdzovať alebo odvolávať transakcie.

Ak používate Spring Boot, vhodný fazuľa typu PlatformTransactionManager sa automaticky zaregistruje, takže to jednoducho musíme vložiť. V opačnom prípade by sme mali manuálne zaregistrovať a PlatformTransactionManager fazuľa.

3.1. Vzorový model domény

Od tejto chvíle budeme kvôli demonštrácii používať zjednodušený model platobnej domény. V tejto jednoduchej doméne máme Platba entita zapuzdriť podrobnosti každej platby:

@Entity public class Payment {@Id @GeneratedValue private Long id; súkromná Dlhá suma; @Column (unique = true) private String referenceNumber; @Enumerated (EnumType.STRING) súkromný štát; // getters and setters public enum State {STARTED, FAILED, SUCCESSFUL}}

Tiež spustíme všetky testy v testovacej triede pomocou knižnice Testcontainers na spustenie inštancie PostgreSQL pred každým testovacím prípadom:

@DataJpaTest @Testcontainers @ActiveProfiles ("test") @AutoConfigureTestDatabase (replace = NONE) @Transactional (propagation = NOT_SUPPORTED) // budeme transakcie vybavovať manuálne verejná trieda ManualTransactionIntegrationTest {@Autowired private PlatformTransactionManager transactionManager; @Autowired private EntityManager entityManager; @ Kontinent súkromný statický PostgreSQLContainer pg = initPostgres (); súkromná TransactionTemplate transactionTemplate; @BeforeEach public void setUp () {transactionTemplate = nový TransactionTemplate (transactionManager); } // testuje súkromné ​​statické PostgreSQLContainer initPostgres () {PostgreSQLContainer pg = nový PostgreSQLContainer ("postgres: 11.1") .wDatabaseName ("baeldung") .withUsername ("test") .withPassword ("test"); pg.setPortBindings (singletonList ("54320: 5432")); návrat pg; }}

3.2. Transakcie s výsledkami

The TransactionTemplate ponúka metódu tzv vykonať, ktorý môže spustiť akýkoľvek daný blok kódu v transakcii a potom vrátiť nejaký výsledok:

@Test void givenAPayment_WhenNotDuplicate_ThenShouldCommit () {Long id = transactionTemplate.execute (status -> {Payment Payment = new Payment (); payment.setAmount (1000L); Payment.setReferenceNumber ("Ref-1"); payment.setState (platba. State.SUCCESSFUL); entityManager.persist (platba); návratová platba.getId ();}); Platba platby = entityManager.find (Payment.class, id); assertThat (platba) .isNotNull (); }

Tu pretrvávame nové Platba inštanciu do databázy a potom vráti svoje automaticky vygenerované ID.

Podobne ako v prípade deklaratívneho prístupu, šablóna môže zaručiť atomicitu pre nás. To znamená, že ak sa nepodarí dokončiť jednu z operácií v transakcii, urobte tovšetky vráti späť:

@Test void givenTwoPayments_WhenRefIsDuplicate_ThenShouldRollback () {try {transactionTemplate.execute (status -> {Payment first = new Payment (); first.setAmount (1000L); first.setReferenceNumber ("Ref-1"); first.setState (Payment.State .SUCCESSFUL); Platba sekunda = nová platba (); second.setAmount (2000L); second.setReferenceNumber ("Ref-1"); // rovnaké referenčné číslo second.setState (Payment.State.SUCCESSFUL); entityManager.persist ( first); // ok entityManager.persist (second); // zlyhá návrat „Ref-1“;}); } catch (Výnimka ignorovaná) {} assertThat (entityManager.createQuery ("vyberte p z platby p"). getResultList ()). isEmpty (); }

Od druhej referenčné číslo je duplikát, databáza odmieta druhú operáciu pretrvávania, čo spôsobí vrátenie celej transakcie. Databáza preto neobsahuje žiadne platby po uskutočnení transakcie. Je tiež možné manuálne spustiť vrátenie volaním volaním setRollbackOnly () na Stav transakcie:

@Test void givenAPayment_WhenMarkAsRollback_ThenShouldRollback () {transactionTemplate.execute (status -> {Payment Payment = new Payment (); Payment.setAmount (1000L); Payment.setReferenceNumber ("Ref-1"); payment.setState (Payment.State.SUCCESSFUL ); entityManager.persist (platba); status.setRollbackOnly (); vrátiť platbu.getId ();}); assertThat (entityManager.createQuery ("vyberte p z platby p"). getResultList ()). isEmpty (); }

3.3. Transakcie bez výsledkov

Ak nemáme v úmysle z transakcie nič vrátiť, môžeme použiť TransactionCallbackWithoutResult trieda spätného volania:

@Test void givenAPayment_WhenNotExpectingAnyResult_ThenShouldCommit () {transactionTemplate.execute (new TransactionCallbackWithoutResult () {@Override chránený void doInTransactionWithoutResult (stav TransactionStatus) {Payment Payment = new Payment (); "Payment.setReferenceN" .State.SUCCESSFUL); entityManager.persist (platba);}}); assertThat (entityManager.createQuery ("vyberte p z platby p"). getResultList ()). hasSize (1); }

3.4. Vlastné konfigurácie transakcií

Doteraz sme používali TransactionTemplate s predvolenou konfiguráciou. Aj keď je toto predvolené nastavenie väčšinou viac ako dosť, stále je možné meniť konfiguračné nastavenia.

Môžeme napríklad nastaviť úroveň izolácie transakcií:

actionTemplate = nový TransactionTemplate (transactionManager); actionTemplate.setIsolationLevel (TransactionDefinition.ISOLATION_REPEATABLE_READ);

Podobne môžeme zmeniť správanie pri šírení transakcie:

actionTemplate.setPropagationBehavior (TransactionDefinition.PROPAGATION_REQUIRES_NEW);

Alebo môžeme nastaviť časový limit transakcie v sekundách:

actionTemplate.setTimeout (1000);

Je dokonca možné ťažiť z optimalizácie pre transakcie iba na čítanie:

actionTemplate.setReadOnly (true);

Každopádne, akonáhle vytvoríme a TransactionTemplate s konfiguráciou všetky transakcie použijú túto konfiguráciu na vykonanie. Takže Ak potrebujeme viac konfigurácií, mali by sme vytvoriť viac inštancií šablón.

4. Používanie PlatformTransactionManager

Navyše k TransactionTemplate, môžeme použiť ešte podobné API na nižšej úrovni PlatformTransactionManager na manuálne spravovanie transakcií. Celkom zaujímavé, obe @ Transakčné a TransactionTemplate používajú toto API na internú správu svojich transakcií.

4.1. Konfigurácia transakcií

Pred použitím tohto API by sme mali definovať, ako bude naša transakcia vyzerať. Napríklad môžeme nastaviť trojsekundový časový limit s úrovňou izolácie opakovateľných transakcií čítania:

DefaultTransactionDefinition definition = nový DefaultTransactionDefinition (); definition.setIsolationLevel (TransactionDefinition.ISOLATION_REPEATABLE_READ); definition.setTimeout (3); 

Definície transakcií sú podobné TransactionTemplate konfigurácie. Avšak, môžeme použiť viac definícií len s jednou PlatformTransactionManager.

4.2. Udržiavanie transakcií

Po nakonfigurovaní našej transakcie môžeme programovo spravovať transakcie:

@Test void givenAPayment_WhenUsingTxManager_ThenShouldCommit () {// definícia transakcie TransactionStatus status = transactionManager.getTransaction (definícia); vyskúšajte {Payment Payment = new Payment (); Payment.setReferenceNumber ("Ref-1"); Payment.setState (Payment.State.SUCCESSFUL); entityManager.persist (platba); transactionManager.commit (stav); } catch (Exception ex) {transactionManager.rollback (status); } assertThat (entityManager.createQuery ("vybrať p z platby p"). getResultList ()). hasSize (1); }

5. Záver

V tomto tutoriáli sme najskôr videli, kedy by sme si mali zvoliť programové riadenie transakcií pred deklaratívnym prístupom. Potom sme sa zavedením dvoch rôznych rozhraní API naučili, ako manuálne vytvárať, potvrdzovať alebo odvolávať vrátené transakcie.

Ako obvykle je vzorový kód k dispozícii na GitHub.


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