Spring @Transactional - isolamento, propagazione


447

Qualcuno può spiegare quali sono i parametri di isolamento e propagazione nell'annotazione @Transactionaltramite un esempio del mondo reale?

Fondamentalmente quando e perché dovrei scegliere di cambiare i loro valori predefiniti.

Risposte:


442

Bella domanda, anche se non banale a cui rispondere.

Propagazione

Definisce la relazione tra le transazioni. Opzioni comuni:

  • Required: Il codice verrà sempre eseguito in una transazione. Crea una nuova transazione o la riutilizza se disponibile.
  • Requires_new: Il codice verrà sempre eseguito in una nuova transazione. Sospende 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 nella stessa transazione, il risultato sarà sempre lo stesso.
  • Serializable: Esegue tutte le transazioni in sequenza.

I diversi livelli hanno caratteristiche prestazionali diverse in un'applicazione multi-thread. Penso che se capisci il dirty readsconcetto sarai in grado di selezionare una buona opzione.


Esempio di quando può verificarsi una lettura sporca:

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

Quindi potrebbe essere un default predefinito (se possibile) Read Committed, che consente solo di leggere i valori che sono già stati impegnati da altre transazioni in esecuzione, in combinazione con un livello di propagazione di Required. Quindi puoi lavorare da lì se la tua applicazione ha altre esigenze.


Un esempio pratico di dove verrà sempre creata una nuova transazione quando si entra nella provideServiceroutine 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 invece utilizzato Required, la transazione rimarrebbe aperta se la transazione fosse già aperta quando si entrava nella routine. Si noti inoltre che il risultato di a rollbackpotrebbe essere diverso in quanto diverse esecuzioni potrebbero prendere parte alla stessa transazione.


Possiamo facilmente verificare il comportamento con un test e vedere in che modo 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 aspettiamo che NON siafooService.provideService() stato ripristinato poiché ha creato una propria sub-transazione.

  • Required: ci aspetteremmo che tutto sia stato ripristinato e il negozio di supporto sia rimasto invariato.


In che modo l'ultimo link si collega a ciò di cui stai parlando? Secondo i documenti collegati, è la sessione che sembra indicare quale sia la transazione corrente, non la factory di sessione.
Donal Fellows,

@Donal, oh scusa non era chiaro. Il punto era che, poiché è sessionFactory.getCurrentTransaction()stato aggiunto, non è più necessario eseguire HibernateTemplateper gestire le transazioni. L'ho rimosso :)
Johan Sjöberg il

La mia domanda era proprio su dove puntava il link, davvero. :-)
Donal Fellows

come ottenere le modifiche apportate nella transazione corrente- stackoverflow.com/questions/36132667/…
Prasanna Kumar HA

304

PROPAGATION_REQUIRED = 0 ; Se DataSourceTransactionObject T1 è già stato avviato per il metodo M1. Se per un altro metodo M2 è richiesto un oggetto Transaction, non viene creato alcun nuovo oggetto Transaction. Lo stesso oggetto T1 viene utilizzato per M2

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

PROPAGATION_REQUIRES_NEW = 3 ; Se DataSourceTransactionObject T1 è già stato avviato per il Metodo M1 ed è in corso (eseguendo il metodo 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 nel proprio contesto di transazione

PROPAGATION_NOT_SUPPORTED = 4 ; Se DataSourceTransactionObject T1 è già avviato per il metodo M1. Se un altro metodo M2 viene eseguito contemporaneamente. Quindi M2 non dovrebbe 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 simultanee e 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. Se sul modo T2 è il rollback, i dati ottenuti da T1 non sono validi. Ad esempio a = 2 sono dati originali. Se T1 legge a = 1 scritto da T2. Se T2 rollback quindi a = 1 verrà eseguito il rollback a a = 2 in DB. Ma, ora, T1 ha un = 1 ma nella tabella DB viene modificato in a = 2.

Scenario2 . Se la transazione T1 legge i dati dalla tabella A1. Se un'altra transazione simultanea (T2) aggiorna i dati nella tabella A1. Quindi i dati che T1 ha letto sono diversi dalla tabella A1. Perché T2 ha aggiornato i dati nella tabella A1.Eg se T1 leggi a = 1 e T2 aggiornato a = 2. Quindi a! = b.

Scenario 3 Se la transazione T1 legge i dati dalla tabella A1 con un determinato numero di righe. Se un'altra transazione simultanea (T2) inserisce più righe nella tabella A1, il numero di righe lette da T1 è diverso dalle righe nella tabella A1

Lo scenario 1 si chiama Letture sporche.

Lo scenario 2 si chiama Letture non ripetibili.

Lo scenario 3 si chiama Phantom reads.

Pertanto, il livello di isolamento è l'estensione alla quale è possibile impedire lo scenario 1, lo scenario 2, lo scenario 3 . È possibile ottenere il livello di isolamento completo implementando il blocco. Ciò impedisce che si verifichino letture e scritture simultanee sugli stessi dati, ma influisce sulle prestazioni. Il livello di isolamento dipende dall'applicazione all'applicazione della quantità di isolamento richiesta.

ISOLATION_READ_UNCOMMITTED : Permette di leggere le modifiche che non sono ancora state impegnate. Soffre di Scenario 1, Scenario 2, Scenario 3

ISOLATION_READ_COMMITTED : consente letture da transazioni simultanee che sono state impegnate. Potrebbe risentire dello scenario 2 e dello scenario 3. Perché altre transazioni potrebbero aggiornare i dati.

ISOLATION_REPEATABLE_READ : letture multiple dello stesso campo produrranno gli stessi risultati fino a quando non viene modificato da solo. Potrebbe risentire dello scenario 3.Perché altre transazioni potrebbero inserire i dati

ISOLATION_SERIALIZABLE : lo scenario 1, lo scenario 2, lo scenario 3 non si verifica mai, è un isolamento completo, comporta un blocco completo e influisce sulle prestazioni a causa del blocco.

Puoi provare 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();
    }

}

È possibile eseguire il debug e vedere il risultato con valori diversi per l'isolamento e la propagazione.


come ottenere le modifiche apportate alla transazione corrente- stackoverflow.com/questions/36132667/…
Prasanna Kumar HA

2
Qual è l'interazione tra livello di isolamento e propagazione ? Se il metodo 1 avvia una transazione con livello di isolamento, ad esempio READ_COMMITTED, e successivamente chiama il metodo 2 con il livello REPEATABLE_READ, sicuramente il metodo 2 deve essere eseguito nella sua nuova transazione, indipendentemente dal comportamento di propagazione specificato (ad esempio, solo RICHIESTO)?
Cornel Masson,

Questo è davvero tardi per lo spettacolo, ma quando PROPAGATION_REQUIRES_NEW, cosa succede a T1 (che viene utilizzato da M1) se si verifica un'altra nuova chiamata a M1? (dire M1.1)
Tim Z.

115

Una spiegazione sufficiente su ciascun parametro è data da altre risposte; Comunque tu abbia chiesto un esempio del mondo reale, ecco quello che chiarisce lo scopo delle diverse opzioni di propagazione :

Supponi di essere responsabile dell'implementazione di un servizio di registrazione in cui viene inviata all'utente un'e-mail di conferma. Si arriva con due oggetti di servizio, uno per l'iscrizione l'utente e uno per l'invio di e-mail, che quest'ultimo è 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 propagazione REQUIRES_NEW e inoltre è probabile che generi un'eccezione (server SMTP inattivo, e-mail non valida o altri motivi). Probabilmente non vuoi che l'intero processo venga ripristinato, come rimuovere le informazioni dell'utente dal database o altre cose; pertanto si chiama 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
}

Ciò significa che ogni volta che viene creato un oggetto DAO, e quindi un potenziale accesso a db, dobbiamo rassicurare che la chiamata è stata effettuata dall'interno di uno dei nostri servizi, il che implica che dovrebbe esistere una transazione live; in caso contrario si verifica un'eccezione. Pertanto la propagazione è di tipo OBBLIGATORIA .


26
Esempio perfetto per REQUIRES_NEW.
Ravi Thapliyal,

5
Buona spiegazione A proposito qual è il valore predefinito per la propagazione? Inoltre sarebbe ancora meglio se potessi dare un esempio come questo anche per l'isolamento. Molte grazie.
Prakash K,

5
@PrakashK L'impostazione predefinita è RICHIESTA. ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… )
ihebiheb

59

Il livello di isolamento definisce in che modo le modifiche apportate ad alcuni repository di dati da una transazione influiscono su altre transazioni simultanee e anche come e quando tali 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 che non sono ancora stati trasferiti da altre transazioni.

Il livello di isolamento READ_COMMITTED indica che una transazione non è in grado di leggere dati non ancora impegnati da altre transazioni.

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

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

La propagazione è la capacità di decidere come incapsulare i metodi di business in transazioni sia logiche che fisiche.

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

REQUIRES_NEW comporta che una nuova transazione fisica verrà sempre creata dal contenitore.

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

Il comportamento OBBLIGATORIO afferma che una transazione aperta esistente deve già esistere. In caso contrario, verrà generata un'eccezione dal contenitore.

Il comportamento MAI indica che una transazione aperta esistente non deve già esistere. Se esiste una transazione, il contenitore genererà un'eccezione.

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

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


4
Se potessi aggiungere quando usare quale, sarebbe molto più vantaggioso.
Kumar Manish,

Fai alcuni esempi, sarebbe molto utile per i principianti
nitinsridar

23

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

In primavera TransactionDefinitioninterfaccia che definisce le proprietà delle transazioni conformi a Spring. @Transactionall'annotazione descrive gli attributi di transazione su un metodo o una classe.

@Autowired
private TestDAO testDAO;

@Transactional(propagation=TransactionDefinition.PROPAGATION_REQUIRED,isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
public void someTransactionalMethod(User user) {

  // Interact with testDAO

}

Propagazione (riproduzione): è usi per relazione tra transazioni. (analogo alla comunicazione inter-thread Java)

+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| 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 (Atomicità, Coerenza, Isolamento, Durabilità) delle transazioni del database. L'isolamento determina come 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 conservazione 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     |
+---------------------------+-------------------+-------------+-------------+------------------------+

Percezione della lettura: si verificano i seguenti 3 tipi di problemi principali:

  • Letture sporche : legge dati non impegnati da un'altra tx (transazione).
  • Letture non ripetibili : letture impegnate UPDATESda un altro tx.
  • Phantom legge : legge impegnato INSERTSe / o DELETESda 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


20

Non vuoi quasi mai usarlo Read Uncommitedpoiché non è veramente ACIDconforme. Read Commmitedè un buon punto di partenza predefinito. Repeatable Readè probabilmente necessario solo in scenari di reporting, rollup o aggregazione. Nota che molti DB, inclusi postgres, in realtà non supportano la lettura ripetibile, devi Serializableinvece usarli . Serializableè utile per le cose che sai che devono accadere completamente indipendentemente da qualsiasi altra cosa; pensalo come synchronizedin Java. Serializable va di pari passo con la REQUIRES_NEWpropagazione.

Uso REQUIRESper tutte le funzioni che eseguono query UPDATE o DELETE, nonché le funzioni di livello "servizio". Per le funzioni di livello DAO che eseguono solo SELECT, io uso SUPPORTSquale parteciperà a un TX se ne è già stato avviato uno (cioè viene chiamato da una funzione di servizio).


13

Transaction Isolation e Transaction Propagation sebbene correlati ma sono chiaramente due concetti molto diversi. In entrambi i casi le impostazioni predefinite vengono personalizzate nel componente di confine del client 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 collegamenti di riferimento di seguito.

Isolamento delle transazioni

Per due o più transazioni / connessioni in esecuzione a un database, come e quando le modifiche apportate dalle query in una transazione hanno un impatto / sono visibili alle query in una transazione diversa. Si riferiva anche al tipo di blocco dei record del database che verrà utilizzato per isolare le modifiche in questa transazione da altre transazioni e viceversa. Questo è in genere implementato dal database / risorsa che partecipa alla transazione.

.

Propagazione delle transazioni

In un'applicazione enterprise per una determinata richiesta / elaborazione ci sono molti componenti che sono coinvolti per completare il lavoro. Alcuni di questi componenti segnano i limiti (inizio / fine) di una transazione che verrà utilizzata nel rispettivo componente e nei suoi componenti secondari. Per questo limite transazionale di componenti, Propagazione transazione specifica se il rispettivo componente parteciperà o meno alla transazione e cosa succede se il componente chiamante ha già o meno una transazione già creata / avviata. È uguale agli attributi di transazione Java EE. Questo è in genere implementato dal gestore transazioni / connessioni client.

Riferimento:


1
Ottimo, tutte le informazioni in un unico posto, i link sono molto utili, grazie @Gladwin Burboz
nitinsridar

7

Ho corso outerMethod, method_1emethod_2 con la modalità di propagazione differenti.

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) -
      • method_2 - Solo annotazione transazione
      • Output: method_1 genererà un'eccezione che nessuna transazione esistente
      • outerMethod - Senza transazione
      • method_1: solo annotazione della 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
      • method_1: solo annotazione della transazione
      • method_2 - Propagation.MANDATORY)
      • Output: method_2 continuerà a registrare nel database.
      • Output: method_1 continuerà a registrare nel database. - Qui principale transazione esistente esterna utilizzata sia per il metodo 1 che 2
      • outerMethod - Con transazione
      • method_1 - Propagation.MANDATORY) -
      • method_2 - Solo l'annotazione della transazione e genera un'eccezione
      • Output: nessun record persiste nel database significa rollback eseguito.
      • 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 method_2 non sia persistente.
      • Output: method_1 continuerà a registrare nel database.
      • Output: non è previsto il rollback per method_1

3

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
    }
}

1

Puoi usare così:

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

Puoi usare questa cosa anche:

public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    boolean isCompleted();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.