Programos tranzakciókezelés tavasszal

1. Áttekintés

Tavaszi @ Tranzakció az annotáció egy szép deklaratív API-t biztosít a tranzakciós határok megjelölésére.

A színfalak mögött egy szempont gondoskodik a tranzakciók létrehozásáról és karbantartásáról, ahogyan azok a @ Tranzakció annotáció. Ez a megközelítés megkönnyíti az alapvető üzleti logikánk elválasztását az olyan horizontális problémáktól, mint a tranzakciókezelés.

Ebben az oktatóanyagban látni fogjuk, hogy ez nem mindig a legjobb megközelítés. Megvizsgáljuk, milyen programozási alternatívákat kínál a tavasz TransactionTemplate, és azok használatának okait.

2. Baj a Paradicsomban

Tegyük fel, hogy két különböző típusú I / O-t keverünk egy egyszerű szolgáltatásban:

@Transactional public void initialPayment (PaymentRequest kérés) {savePaymentRequest (request); // DB callThePaymentProviderApi (kérés); // API updatePaymentState (kérés); // DB saveHistoryForAuditing (kérés); // DB}

Itt van néhány adatbázis-hívás egy esetleg drága REST API-hívás mellett. Első ránézésre érdemes lehet a teljes módszert tranzakcióvá tenni, mivel érdemes használni EntityManager hogy az egész műveletet atomi módon hajtsa végre.

Ha azonban ennek a külső API-nak bármilyen okból kifolyólag a válaszolása a szokásosnál hosszabb ideig tart, hamarosan elfogyhatnak az adatbázis-kapcsolatok!

2.1. A valóság durva természete

Itt történik, mi történik, amikor felhívjuk a kezdeti fizetés módszer:

  1. A tranzakciós szempont újat hoz létre EntityManager és új tranzakciót indít - tehát felvesz egyet Kapcsolat a csatlakozási medencéből
  2. Az első adatbázis-hívás után felhívja a külső API-t, miközben a kölcsönzöttet megtartja Kapcsolat
  3. Végül ezt használja Kapcsolat a fennmaradó adatbázis-hívások végrehajtására

Ha az API-hívás egy ideig nagyon lassan reagál, ez a módszer a kölcsönvetteket serkenti Kapcsolat miközben várta a választ.

Képzelje el, hogy ebben az időszakban egy sor hívást kapunk a kezdeti fizetés módszer. Aztán minden Kapcsolatok várhat az API-hívás válaszára. Ezért elfogyhatnak az adatbázis-kapcsolatok - a lassú háttérszolgáltatás miatt!

Az adatbázis I / O keverése más típusú I / O tranzakciós kontextusban rossz szag. Tehát az ilyen jellegű problémák első megoldása az ilyen típusú I / O teljes elválasztása. Ha bármilyen okból nem tudjuk szétválasztani őket, akkor is használhatjuk a Spring API-kat a tranzakciók kézi kezeléséhez.

3. Használata TransactionTemplate

TransactionTemplate visszahívás-alapú API-kat kínál a tranzakciók kézi kezeléséhez. Használatához először inicializálnunk kell a PlatformTransactionManager.

Beállíthatjuk például ezt a sablont a függőség-injektálás segítségével:

// test annotations class ManualTransactionIntegrationTest {@Autowired private PlatformTransactionManageractionManager; private TransactionTemplateactionTemplate; @BeforeEach void setUp () {tranzakcióminta = új Tranzakcióminta (tranzakciókezelő); } // kihagyva}

A PlatformTransactionManager segíti a sablont tranzakciók létrehozásában, végrehajtásában vagy visszagörgetésében.

A Spring Boot használatakor megfelelő típusú babot PlatformTransactionManager automatikusan regisztrálásra kerül, ezért egyszerűen be kell adnunk. Ellenkező esetben manuálisan regisztrálnunk kell a PlatformTransactionManager bab.

3.1. Minta tartománymodell

Mostantól a bemutatás érdekében egyszerűsített fizetési tartományi modellt fogunk használni. Ebben az egyszerű tartományban van egy Fizetés egység az egyes kifizetések részleteinek összefoglalására:

@Entity public class Payment {@Id @GeneratedValue private Long id; magán Hosszú összeg; @ Oszlop (egyedi = igaz) privát karakterlánc hivatkozási szám; @ Számolt (EnumType.STRING) magánállam állam; // getters and setters public enum State {STARTED, FAILED, SUCCESSFUL}}

Ezenkívül minden tesztet lefuttatunk egy tesztosztályon belül, a Testcontainers könyvtár segítségével PostgreSQL példányt futtatunk minden teszteset előtt:

@DataJpaTest @Testcontainers @ActiveProfiles ("test") @AutoConfigureTestDatabase (csere = NINCS) @Transactional (propagation = NOT_SUPPORTED) // a tranzakciókat manuálisan fogjuk kezelni. @Autowired private EntityManager entitásManager; @Container privát statikus PostgreSQLContainer pg = initPostgres (); private TransactionTemplateactionTemplate; @BeforeEach public void setUp () {actionTemplate = new TransactionTemplate (actionManager); } // privát statikus PostgreSQLContainer initPostgres () tesztet tesztel pg.setPortBindings (singletonList ("54320: 5432")); return pg; }}

3.2. Tranzakciók eredményekkel

A TransactionTemplate nevű módszert kínál végrehajtani, amely egy adott kódblokkot futtathat egy tranzakción belül, majd eredményt adhat vissza:

@Test void givenAPayment_WhenNotDuplicate_ThenShouldCommit () {Hosszú id = tranzakcióTemplate.execute (állapot -> {Fizetési fizetés = új fizetés (); payment.setAmount (1000L); payment.setReferenceNumber ("Ref-1"); payment.setState (Payment. State.SUCCESSFUL); entitásManager.persist (fizetés); return fizetés.getId ();}); Fizetési fizetés = entityManager.find (Payment.class, id); assertThat (fizetés) .isNotNull (); }

Itt tartunk fenn egy új Fizetés példányt az adatbázisba, majd visszaküldi annak automatikusan létrehozott azonosítóját.

A deklaratív megközelítéshez hasonlóan a sablon garantálja az atomosságot nekünk. Vagyis, ha egy tranzakción belül az egyik műveletet nem sikerül végrehajtani, akkor aztvisszagurítja mindegyiket:

@Test void givenTwoPayments_WhenRefIsDuplicate_ThenShouldRollback () {try {tranzakcióTemplate.execute (status -> {Payment first = new Payment (); first.setAmount (1000L); first.setReferenceNumber ("Ref-1"); first.setState (Payment.State) .SUCCESSFUL); Második fizetés = new Payment (); second.setAmount (2000L); second.setReferenceNumber ("Ref-1"); // ugyanaz a hivatkozási szám second.setState (Payment.State.SUCCESSFUL); entitásManager.persist ( első); // ok entitásManager.persist (második); // nem adja vissza a "Ref-1" -et;}); } catch (kivétel figyelmen kívül hagyva) {} assertThat (entityManager.createQuery ("p kiválasztása a fizetés p közül"). getResultList ()). isEmpty (); }

A második óta Referenciaszám egy másolat, az adatbázis elutasítja a második persist műveletet, ami az egész tranzakció visszagörgetését eredményezi. Ezért az adatbázis nem tartalmaz fizetést a tranzakció után. A visszahívás manuálisan is kiváltható a setRollbackOnly () tovább TransactionStatus:

@Test void givenAPayment_WhenMarkAsRollback_ThenShouldRollback () {actionTemplate.execute (status -> {Payment payment = new Payment (); payment.setAmount (1000L); payment.setReferenceNumber ("Ref-1"); payment.setState (Payment.State.SUCCESSFUL ); entityManager.persist (fizetés); status.setRollbackOnly (); return payment.getId ();}); assertThat (entitásManager.createQuery ("p kiválasztása a fizetés p közül"). getResultList ()). isEmpty (); }

3.3. Eredmények nélküli tranzakciók

Ha nem szándékozunk semmit visszaküldeni a tranzakcióból, használhatjuk a TransactionCallbackWithoutResult visszahívási osztály:

@Test void givenAPayment_WhenNotExpectingAnyResult_ThenShouldCommit () {actionTemplate.execute (new TransactionCallbackWithoutResult () {@Override protected void doInTransactionWithoutResult (TransactionStatus status) {Payment payment = new Payment (); payment.set; (payment.setReference); payment.set; .State.SUCCESSFUL); entityManager.persist (fizetés);}}); assertThat (entitásManager.createQuery ("p kiválasztása a fizetés p közül"). getResultList ()). hasSize (1); }

3.4. Egyéni tranzakciók konfigurációi

Mostanáig a TransactionTemplate alapértelmezett konfigurációjával. Bár ez az alapértelmezés többnyire elegendő, a konfigurációs beállításokat mégis meg lehet változtatni.

Beállíthatjuk például a tranzakció elkülönítési szintjét:

ügyletTemplate = új TransactionTemplate (tranzakcióManager); ügyletTemplate.setIsolationLevel (TransactionDefinition.ISOLATION_REPEATABLE_READ);

Hasonlóképpen megváltoztathatjuk a tranzakció terjedési viselkedését:

ügyletTemplate.setPropagationBehavior (TransactionDefinition.PROPAGATION_REQUIRES_NEW);

Vagy beállíthatjuk a tranzakció időkorlátját másodpercben:

ügyletTemplate.setTimeout (1000);

Még csak olvasható tranzakciók optimalizálásának előnyeit is kihasználhatja:

ügyletTemplate.setReadOnly (true);

Mindenesetre, ha létrehozunk egy TransactionTemplate egy konfigurációval az összes tranzakció végrehajtja ezt a konfigurációt. Így, ha több konfigurációra van szükségünk, akkor több sablonpéldányt kell létrehoznunk.

4. Használata PlatformTransactionManager

Amellett, hogy a TransactionTemplate, használhatunk még alacsonyabb szintű API-t is PlatformTransactionManager a tranzakciók kézi kezeléséhez. Nagyon érdekes módon mindkettő @ Tranzakció és TransactionTemplate használja ezt az API-t tranzakcióik belső kezelésére.

4.1. Tranzakciók konfigurálása

Az API használata előtt meg kell határoznunk, hogy fog kinézni a tranzakciónk. Beállíthatunk például egy három másodperces időkorlátot az ismételhető olvasási tranzakció izolációs szinttel:

DefaultTransactionDefinition meghatározása = new DefaultTransactionDefinition (); definition.setIsolationLevel (TransactionDefinition.ISOLATION_REPEATABLE_READ); definíció.setTimeout (3); 

A tranzakciók definíciói hasonlóak a TransactionTemplate konfigurációk. azonban, több definíciót használhatunk csak egy PlatformTransactionManager.

4.2. Tranzakciók fenntartása

A tranzakciónk konfigurálása után programozottan kezelhetjük a tranzakciókat:

@Test void givenAPayment_WhenUsingTxManager_ThenShouldCommit () {// tranzakciódefiníció TransactionStatus status = tranzakcióManager.getTransaction (meghatározás); próbáld ki a {Payment payment = new Payment (); payment.setReferenceNumber ("Ref-1"); payment.setState (Payment.State.SIKERES); entityManager.persist (fizetés); tranzakcióManager.commit (állapot); } catch (Exception ex) {actionManager.rollback (status); } assertThat (entityManager.createQuery ("p kiválasztása a fizetés p közül"). getResultList ()). hasSize (1); }

5. Következtetés

Ebben az oktatóanyagban először azt láttuk, hogy mikor kell választani az automatizált tranzakciókezelést a deklaratív megközelítés helyett. Ezután két különböző API bevezetésével megtanultuk, hogyan lehet manuálisan létrehozni, végrehajtani vagy visszagörgetni az adott tranzakciókat.

Szokás szerint a mintakód elérhető a GitHubon.


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