Spring @Transactional: isolamento, propagazione

Qualcuno può spiegare che cosa sono i parametri di isolamento e propagazione nell’annotazione @Transactional tramite l’esempio del mondo reale. Fondamentalmente quando e perché dovrei scegliere di cambiare i loro valori predefiniti.

Bella domanda, anche se non banale da rispondere.

Propagazione

Definisce come le transazioni si relazionano tra loro. Opzioni comuni

  • Required : il codice verrà sempre eseguito in una transazione. Crea una nuova transazione o riutilizzala, se disponibile.
  • Requires_new : il codice verrà sempre eseguito in una nuova transazione. Sospendi la transazione corrente se ne esiste una.

Isolamento

Definisce il contratto dati tra le transazioni.

  • Read Uncommitted : consente letture sporche
  • Read Committed : non consente letture sporche
  • Repeatable Read : se una riga viene letta due volte nello stesso transaciton, il risultato sarà sempre lo stesso
  • Serializable : esegue tutte le transazioni in una sequenza

I diversi livelli hanno caratteristiche di prestazione diverse in un’applicazione multi-thread. Penso che se capisci il concetto di dirty reads sarai in grado di selezionare una buona opzione.


Esempio quando può verificarsi una lettura sporca

  thread 1 thread 2 | | write(x) | | | | read(x) | | rollback | vv value (x) is now dirty (incorrect) 

Quindi un predefinito Read Comitted (se tale può essere richiesto) potrebbe essere Read Comitted , che consente solo di leggere i valori che sono già stati comunicati da altre transazioni in esecuzione, in combinazione con un livello di propagazione di Required . Quindi puoi lavorare da lì se l’applicazione ha altri bisogni.


Un esempio pratico in cui una nuova transazione verrà sempre creata quando si entra nella routine provideService e completata quando si esce.

 public class FooService { private Repository repo1; private Repository repo2; @Transactional(propagation=Propagation.REQUIRES_NEW) public void provideService() { repo1.retrieveFoo(); repo2.retrieveFoo(); } } 

Se avessimo usato Required la transazione rimarrà aperta se la transazione era già aperta quando si entra nella routine. Si noti inoltre che il risultato di un rollback potrebbe essere diverso in quanto diverse esecuzioni potrebbero prendere parte alla stessa transazione.


Possiamo facilmente verificare il comportamento con un test e vedere come i risultati differiscono con i livelli di propagazione

 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:/fooService.xml") public class FooServiceTests { private @Autowired TransactionManager transactionManager; private @Autowired FooService fooService; @Test public void testProvideService() { TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); fooService.provideService(); transactionManager.rollback(status); // assert repository values are unchanged ... } 

Con un livello di propagazione di

  • Requires new ci si aspetterebbe che fooService.provideService() NON sia stato ripristinato poiché ha creato la sua sub-transazione.

  • Required ci aspetteremmo che tutto sia stato ripristinato e il backing store invariato.

PROPAGATION_REQUIRED = 0 ; Se DataSourceTransactionObject T1 è già stato avviato per il Metodo M1.If per un altro object Metodo M2 Transaction è necessario, non viene creato alcun nuovo object Transaction. L’object T1 dell’object viene utilizzato per M2

PROPAGATION_MANDATORY = 2 ; il metodo deve essere eseguito all’interno di una transazione. Se nessuna transazione esistente è in corso, verrà generata un’eccezione

PROPAGATION_REQUIRES_NEW = 3 ; Se DataSourceTransactionObject T1 è già stato avviato per il Metodo M1 ed è in corso (metodo di esecuzione M1). Se un altro metodo M2 inizia l’esecuzione, T1 viene sospeso per la durata del metodo M2 con il nuovo DataSourceTransactionObject T2 per M2.M2 eseguito all’interno del proprio contesto di transazione

PROPAGATION_NOT_SUPPORTED = 4 ; Se DataSourceTransactionObject T1 è già stato avviato per il Metodo M1. Se un altro metodo M2 viene eseguito contemporaneamente. Quindi M2 non deve essere eseguito nel contesto della transazione. T1 è sospeso fino al termine di M2.

PROPAGATION_NEVER = 5 ; Nessuno dei metodi viene eseguito nel contesto della transazione.

Un livello di isolamento: si tratta di quanto una transazione può essere influenzata dalle attività di altre transazioni concorrenti. Supporta la coerenza lasciando i dati su molte tabelle in uno stato coerente. Implica il blocco di righe e / o tabelle in un database.

Il problema con più transazioni

Scenario 1. Se la transazione T1 legge i dati dalla tabella A1 che è stata scritta da un’altra transazione concorrente T2.If sul modo in cui T2 è rollback, i dati ottenuti da T1 non sono validi. E a a 2 è dati originali. Se T1 legge a = 1 che è stato scritto da T2.Se il rollback T2 allora a = 1 sarà rollback a a = 2 in DB.Ma, ora, T1 ha a = 1 ma nella tabella DB è cambiato in a = 2.

Scenario2 . Se la transazione T1 legge i dati dalla tabella A1.Se un’altra transazione concorrente (T2) aggiorna i dati sulla tabella A1.Quindi i dati che T1 ha letto è diverso dalla tabella A1.Perché T2 ha aggiornato i dati sulla tabella A1.Eg se T1 leggi a = 1 e T2 aggiorna a = 2. Quindi a! = b.

Scenario 3. Se la transazione T1 legge i dati dalla tabella A1 con un certo numero di righe. Se un’altra transazione concorrente (T2) inserisce più righe nella tabella A1.Il numero di righe lette da T1 è diverso da righe nella tabella A1

Lo scenario 1 si chiama letture sporche.

Lo scenario 2 è chiamato letture non ripetibili.

Lo scenario 3 è chiamato letture Phantom.

Quindi, il livello di isolamento è l’estensione a cui è ansible prevenire Scenario 1, Scenario 2, Scenario 3 . È ansible ottenere il livello di isolamento completo implementando il blocco. Ciò impedisce il verificarsi di letture e scritture contemporanee agli stessi dati. Tuttavia influisce sulle prestazioni. Il livello di isolamento dipende dall’applicazione all’applicazione quanto è richiesto l’isolamento.

ISOLATION_READ_UNCOMMITTED : consente di leggere le modifiche che non sono ancora state commesse. Ne risentono Scenario 1, Scenario 2, Scenario 3

ISOLATION_READ_COMMITTED : consente di leggere da transazioni simultanee che sono state commesse. Potrebbe risentire dello scenario 2 e dello scenario 3. Poiché altre transazioni potrebbero aggiornare i dati.

ISOLATION_REPEATABLE_READ : letture multiple dello stesso campo produrranno gli stessi risultati fino a quando non verranno modificate da sole. Potrebbero risentire dello scenario 3. Perché altre transazioni potrebbero inserire i dati

ISOLATION_SERIALIZABLE : Scenario 1, Scenario 2, Scenario 3 non avviene mai. È completo isolamento. Impone il blocco totale. Ottiene performace a causa del blocco.

Puoi testare usando

 public class TransactionBehaviour { // set is either using xml Or annotation DataSourceTransactionManager manager=new DataSourceTransactionManager(); SimpleTransactionStatus status=new SimpleTransactionStatus(); ; public void beginTransaction() { DefaultTransactionDefinition Def = new DefaultTransactionDefinition(); // overwrite default PROPAGATION_REQUIRED and ISOLATION_DEFAULT // set is either using xml Or annotation manager.setPropagationBehavior(XX); manager.setIsolationLevelName(XX); status = manager.getTransaction(Def); } public void commitTransaction() { if(status.isCompleted()){ manager.commit(status); } } public void rollbackTransaction() { if(!status.isCompleted()){ manager.rollback(status); } } Main method{ beginTransaction() M1(); If error(){ rollbackTransaction() } commitTransaction(); } } 

È ansible eseguire il debug e visualizzare il risultato con valori diversi per l’isolamento e la propagazione.

Basta spiegazioni su ciascun parametro fornite da altre risposte; Comunque hai chiesto un esempio del mondo reale, ecco quello che chiarisce lo scopo delle diverse opzioni di propagazione :

Supponiamo che tu abbia il compito di implementare un servizio di registrazione in cui viene inviata una e-mail di conferma all’utente. Vieni con due oggetti di servizio, uno per l’ iscrizione dell’utente e uno per l’ invio di e-mail, che quest’ultimo viene chiamato all’interno del primo. Ad esempio qualcosa del genere:

 /* Sign Up service */ @Service @Transactional(Propagation=REQUIRED) class SignUpService{ ... void SignUp(User user){ ... emailService.sendMail(User); } } /* E-Mail Service */ @Service @Transactional(Propagation=REQUIRES_NEW) class EmailService{ ... void sendMail(User user){ try{ ... // Trying to send the e-mail }catch( Exception) } } 

Potresti aver notato che il secondo servizio è di tipo di propagazione REQUIRES_NEW e inoltre è probabile che lanci un’eccezione (server SMTP inattivo, e-mail non valida o altri motivi). Probabilmente non vuoi che l’intero processo ritorni indietro, come rimuovere le informazioni utente dal database o altre cose; pertanto chiami il secondo servizio in una transazione separata.

Tornando al nostro esempio, questa volta ti preoccupi della sicurezza del database, quindi definisci le tue classi DAO in questo modo:

 /* User DAO */ @Transactional(Propagation=MANDATORY) class UserDAO{ // some CRUD methods } 

Il che significa che ogni volta che viene creato un object DAO, e quindi un potenziale accesso a db, dobbiamo rassicurare che la chiamata è stata fatta all’interno di uno dei nostri servizi, il che implica che dovrebbe esistere una transazione live; altrimenti si verifica un’eccezione. Pertanto la propagazione è di tipo OBBLIGATORIO .

Il livello di isolamento definisce in che modo le modifiche apportate a un data repository da una transazione influiscono su altre transazioni simultanee simultanee e anche su come e quando i dati modificati diventano disponibili per altre transazioni. Quando definiamo una transazione utilizzando il framework Spring, siamo anche in grado di configurare in quale livello di isolamento verrà eseguita la stessa transazione.

 @Transactional(isolation=Isolation.READ_COMMITTED) public void someTransactionalMethod(Object obj) { } 

Il livello di isolamento READ_UNCOMMITTED indica che una transazione può leggere dati non ancora trasmessi da altre transazioni.

Il livello di isolamento READ_COMMITTED indica che una transazione non può leggere dati non ancora impegnati da altre transazioni.

Il livello di isolamento REPEATABLE_READ indica che se una transazione legge un record dal database più volte il risultato di tutte quelle operazioni di lettura deve essere sempre lo stesso.

Il livello di isolamento SERIALIZABLE è il più restrittivo tra tutti i livelli di isolamento. Le transazioni vengono eseguite con il blocco a tutti i livelli (lettura, intervallo e blocco di scrittura) in modo che appaiano come se fossero eseguite in modo serializzato.

La propagazione è la capacità di decidere in che modo i metodi di business dovrebbero essere incapsulati in transazioni sia logiche che fisiche.

Il comportamento Spring REQUIRED indica che verrà utilizzata la stessa transazione se esiste una transazione già aperta nel contesto di esecuzione del metodo bean corrente.

Il comportamento REQUIRES_NEW significa che verrà sempre creata una nuova transazione fisica dal contenitore.

Il comportamento NESTED consente alle transazioni Spring annidate di utilizzare la stessa transazione fisica ma imposta i punti di salvataggio tra le invocazioni nidificate in modo che le transazioni interne possano anche eseguire il rollback indipendentemente dalle transazioni esterne.

Il comportamento OBBLIGATORIO afferma che una transazione aperta esistente deve già esistere. Se non viene generata un’eccezione dal contenitore.

Il comportamento MAI afferma che una transazione aperta esistente non deve già esistere. Se esiste una transazione, verrà generata un’eccezione dal contenitore.

Il comportamento NOT_SUPPORTED verrà eseguito al di fuori dell’ambito di qualsiasi transazione. Se una transazione aperta esiste già, verrà messa in pausa.

Il comportamento SUPPORTS verrà eseguito nell’ambito di una transazione se esiste già una transazione aperta. Se non c’è una transazione già aperta, il metodo verrà eseguito comunque ma in modo non transazionale.

Non si vuole quasi mai usare Read Uncommited poiché non è realmente compatibile con ACID . Read Commmited è un buon punto di partenza predefinito. Repeatable Read è probabilmente necessaria solo negli scenari di reporting, rollup o aggregazione. Si noti che molti DB, postgres inclusi in realtà non supportano la lettura ripetibile, è necessario utilizzare Serializable . Serializable è utile per cose che sai che devono accadere completamente indipendentemente da qualsiasi altra cosa; pensalo come synchronized in Java. Serializable va di pari passo con la propagazione REQUIRES_NEW .

Utilizzo REQUIRES per tutte le funzioni che eseguono query UPDATE o DELETE nonché funzioni di livello “servizio”. Per le funzioni di livello DAO che eseguono solo SELECT, utilizzo SUPPORTS che parteciperanno a un TX se uno è già stato avviato (ovvero viene chiamato da una funzione di servizio).

Isolamento delle transazioni e Propagazione delle transazioni benché correlate ma sono chiaramente due concetti molto diversi. In entrambi i casi i valori predefiniti sono personalizzati in base al componente del limite del cliente utilizzando la gestione delle transazioni dichiarativa o la gestione delle transazioni programmatiche . I dettagli di ciascun livello di isolamento e attributi di propagazione sono disponibili nei link di riferimento di seguito.

Isolamento della transazione

Per informazioni su due o più transazioni / connessioni in esecuzione a un database, come e quando le modifiche apportate da query in una transazione hanno impatto / visibilità per le query in una transazione diversa. Ha anche riguardato il tipo di blocco del record del database che verrà utilizzato per isolare le modifiche in questa transazione da altre transazioni e viceversa. Questo in genere viene implementato dal database / risorsa che partecipa alla transazione.

.

Propagazione della transazione

In un’applicazione enterprise per qualsiasi richiesta / elaborazione, ci sono molte componenti che sono coinvolte per portare a termine il lavoro. Alcuni di questi componenti segnano i limiti (inizio / fine) di una transazione che verrà utilizzata nel rispettivo componente e nelle sue sotto componenti. Per questo limite transazionale di componenti, Transaction Propogation specifica se il rispettivo componente parteciperà o meno alla transazione e cosa succede se il componente di chiamata ha già o non ha una transazione già creata / avviata. Questo è lo stesso degli attributi di transazione Java EE. Questo in genere viene implementato dal gestore transazioni / connessione client.

Riferimento:

  • Gestione delle transazioni di spring

  • Wiki Transaction Isolation (sistemi di database)

  • Oracle sui livelli di isolamento delle transazioni

  • Attributi di transazione Java EE (propagazione)

  • Propagazione della transazione di Spring Framework

Ho eseguito outerMethod , method_1 e method_2 con diversa modalità di propagazione.

Di seguito è riportato l’output per diverse modalità di propagazione.

  • Metodo esterno

     @Transactional @Override public void outerMethod() { customerProfileDAO.method_1(); iWorkflowDetailDao.method_2(); } 
  • Method_1

     @Transactional(propagation=Propagation.MANDATORY) public void method_1() { Session session = null; try { session = getSession(); Temp entity = new Temp(0l, "XXX"); session.save(entity); System.out.println("Method - 1 Id "+entity.getId()); } finally { if (session != null && session.isOpen()) { } } } 
  • Method_2

     @Transactional() @Override public void method_2() { Session session = null; try { session = getSession(); Temp entity = new Temp(0l, "CCC"); session.save(entity); int i = 1/0; System.out.println("Method - 2 Id "+entity.getId()); } finally { if (session != null && session.isOpen()) { } } } 
      • outerMethod – Senza transazione
      • method_1 – Propagation.MANDATORY) –
      • metodo_2 – Solo annotazione transazione
      • Output: method_1 genererà un’eccezione che nessuna transazione esistente
      • outerMethod – Senza transazione
      • metodo_1 – Solo annotazione transazione
      • method_2 – Propagation.MANDATORY)
      • Output: method_2 genererà un’eccezione che nessuna transazione esistente
      • Output: method_1 continuerà a registrare nel database.
      • outerMethod – Con transazione
      • metodo_1 – Solo annotazione transazione
      • method_2 – Propagation.MANDATORY)
      • Output: method_2 continuerà a registrare nel database.
      • Output: method_1 continuerà a registrare nel database. – Qui la transazione esistente Main Outer utilizzata per entrambi i metodi 1 e 2
      • outerMethod – Con transazione
      • method_1 – Propagation.MANDATORY) –
      • metodo_2 – Solo annotazione transazione e genera eccezione
      • Output: nessun record persistente nel database significa rollback fatto.
      • outerMethod – Con transazione
      • method_1 – Propagation.REQUIRES_NEW)
      • method_2 – Propagation.REQUIRES_NEW) e genera un’eccezione 1/0
      • Output: method_2 genererà un’eccezione in modo che il record di method_2 non venga mantenuto.
      • Output: method_1 continuerà a registrare nel database.
      • Output: non esiste il rollback per method_1

Possiamo aggiungere per questo:

 @Transactional(readOnly = true) public class Banking_CustomerService implements CustomerService { public Customer getDetail(String customername) { // do something } // these settings have precedence for this method @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) public void updateCustomer(Customer customer) { // do something } } 

Una transazione rappresenta un’unità di lavoro con un database.

In spring l’interfaccia TransactionDefinition che definisce le proprietà della transazione conformi a Spring. @Transactional annotazione transazionale descrive gli attributi della transazione su un metodo o una class.

 @Autowired private TestDAO testDAO; @Transactional(propagation=TransactionDefinition.PROPAGATION_REQUIRED,isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED) public void someTransactionalMethod(User user) { // Interact with testDAO } 

Propagazione (riproduzione): è utilizzabile per la relazione tra transazioni. (analogo alla comunicazione java inter thread)

 +-------+---------------------------+------------------------------------------------------------------------------------------------------+ | value | Propagation | Description | +-------+---------------------------+------------------------------------------------------------------------------------------------------+ | -1 | TIMEOUT_DEFAULT | Use the default timeout of the underlying transaction system, or none if timeouts are not supported. | | 0 | PROPAGATION_REQUIRED | Support a current transaction; create a new one if none exists. | | 1 | PROPAGATION_SUPPORTS | Support a current transaction; execute non-transactionally if none exists. | | 2 | PROPAGATION_MANDATORY | Support a current transaction; throw an exception if no current transaction exists. | | 3 | PROPAGATION_REQUIRES_NEW | Create a new transaction, suspending the current transaction if one exists. | | 4 | PROPAGATION_NOT_SUPPORTED | Do not support a current transaction; rather always execute non-transactionally. | | 5 | PROPAGATION_NEVER | Do not support a current transaction; throw an exception if a current transaction exists. | | 6 | PROPAGATION_NESTED | Execute within a nested transaction if a current transaction exists. | +-------+---------------------------+------------------------------------------------------------------------------------------------------+ 

Isolamento: l’ isolamento è una delle proprietà ACID (Atomicity, Consistency, Isolation, Durability) delle transazioni del database. L’isolamento determina in che modo l’integrità della transazione è visibile ad altri utenti e sistemi. Utilizza per il blocco delle risorse, ad esempio il controllo della concorrenza, assicurarsi che solo una transazione possa accedere alla risorsa in un determinato punto.

Percezione di blocco: il livello di isolamento determina la durata della permanenza dei blocchi.

 +---------------------------+-------------------+-------------+-------------+------------------------+ | Isolation Level Mode | Read | Insert | Update | Lock Scope | +---------------------------+-------------------+-------------+-------------+------------------------+ | READ_UNCOMMITTED | uncommitted data | Allowed | Allowed | No Lock | | READ_COMMITTED (Default) | committed data | Allowed | Allowed | Lock on Committed data | | REPEATABLE_READ | committed data | Allowed | Not Allowed | Lock on block of table | | SERIALIZABLE | committed data | Not Allowed | Not Allowed | Lock on full table | +---------------------------+-------------------+-------------+-------------+------------------------+ 

Leggi la percezione: i seguenti 3 tipi di problemi principali si verificano:

  • Letture sporche : legge dati non inviati da un altro tx (transazione).
  • Letture non ripetibili : legge gli UPDATES impegnati da un altro tx.
  • Phantom legge : legge DELETES e / o DELETES da un altro tx

Livelli di isolamento con diversi tipi di letture:

 +---------------------------+----------------+----------------------+----------------+ | Isolation Level Mode | Dirty reads | Non-repeatable reads | Phantoms reads | +---------------------------+----------------+----------------------+----------------+ | READ_UNCOMMITTED | allows | allows | allows | | READ_COMMITTED (Default) | prevents | allows | allows | | REPEATABLE_READ | prevents | prevents | allows | | SERIALIZABLE | prevents | prevents | prevents | +---------------------------+----------------+----------------------+----------------+ 

per esempio

Puoi usare in questo modo:

 @Transactional(propagation = Propagation.REQUIRES_NEW) public EventMessage activate(EventMessage eventMessage) { //here some transaction related code } 

Puoi usare anche questa cosa:

 public interface TransactionStatus extends SavepointManager { boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); void flush(); boolean isCompleted(); }