Spring @Transactional – isolation, propagation

Quelqu’un peut-il expliquer à quoi servent les parameters d’ isolation et de propagation dans l’annotation @Transactional via l’exemple du monde réel? Fondamentalement, quand et pourquoi je devrais choisir de changer leurs valeurs par défaut.

Bonne question, mais pas sortingviale à répondre.

Propagation

Définit la relation entre les transactions. Options communes

  • Required : le code sera toujours exécuté dans une transaction. Créez une nouvelle transaction ou réutilisez-en une si disponible.
  • Requires_new : le code sera toujours exécuté dans une nouvelle transaction. Suspendre la transaction en cours s’il en existe une.

Isolement

Définit le contrat de données entre les transactions.

  • Read Uncommitted : Autorise les lectures sales
  • Read Committed : Ne permet pas les lectures sales
  • Repeatable Read : Si une ligne est lue deux fois dans la même transaction, le résultat sera toujours le même
  • Serializable : Effectue toutes les transactions dans une séquence

Les différents niveaux ont des caractéristiques de performance différentes dans une application multithread. Je pense que si vous comprenez le concept des dirty reads vous pourrez choisir une bonne option.


Exemple de lecture incorrecte

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

Ainsi, une valeur par défaut (si possible, par exemple) pourrait être Read Comitted , ce qui vous permet uniquement de lire des valeurs qui ont déjà été validées par d’autres transactions en cours d’exécution, en combinaison avec un niveau de propagation Required . Ensuite, vous pouvez travailler à partir de là si votre application a d’autres besoins.


Un exemple pratique où une nouvelle transaction sera toujours créée lors de la saisie de la routine provideService et terminée lors du départ.

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

Si nous avions utilisé Required la transaction restra ouverte si la transaction était déjà ouverte lors de la saisie de la routine. Notez également que le résultat d’une rollback pourrait être différent car plusieurs exécutions pourraient prendre part à la même transaction.


Nous pouvons facilement vérifier le comportement avec un test et voir comment les résultats diffèrent avec les niveaux de propagation

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

Avec un niveau de propagation de

  • Requires new on s’attendrait à ce que fooService.provideService() n’ait PAS été fooService.provideService() car il a créé sa propre sous-transaction.

  • Nous nous attendions à ce que tout soit annulé et sauvegardé sans modification.

PROPAGATION_REQUIRED = 0 ; Si DataSourceTransactionObject T1 est déjà démarré pour la méthode M1.If pour un autre object Method M2 Transaction est requirejs, aucun nouvel object Transaction n’est créé. L’object T1 est utilisé pour M2

PROPAGATION_MANDATORY = 2 ; La méthode doit être exécutée dans une transaction. Si aucune transaction existante n’est en cours, une exception sera lancée

PROPAGATION_REQUIRES_NEW = 3 ; Si DataSourceTransactionObject T1 est déjà lancé pour la méthode M1 et qu’il est en cours d’exécution (méthode d’exécution M1). Si une autre méthode M2 ​​commence à s’exécuter, T1 est suspendue pendant la durée de la méthode M2 ​​avec le nouveau DataSourceTransactionObject T2 pour M2.M2

PROPAGATION_NOT_SUPPORTED = 4 ; Si DataSourceTransactionObject T1 est déjà démarré pour la méthode M1.Si une autre méthode M2 ​​est exécutée simultanément, alors M2 ne doit pas s’exécuter dans le contexte de la transaction. T1 est suspendu jusqu’à ce que M2 soit terminé.

PROPAGATION_NEVER = 5 ; Aucune des méthodes ne s’exécute dans le contexte de la transaction.

Un niveau d’isolement: il s’agit de savoir dans quelle mesure une transaction peut être affectée par les activités d’autres transactions simultanées. Elle prend en charge la cohérence en laissant les données sur plusieurs tables dans un état cohérent. Cela implique le locking des lignes et / ou des tables dans une firebase database.

Le problème avec les transactions multiples

Scénario 1 .Si la transaction T1 lit les données de la table A1 écrites par une autre transaction simultanée T2.If sur la manière dont T2 est annulée, les données obtenues par T1 ne sont pas valides.Eg a = 2 correspond aux données d’origine. Si T1 lit a = 1 qui a été écrit par T2.If T2 rollback alors a = 1 sera un retour à a = 2 dans DB.But, maintenant, T1 a a = 1 mais dans la table DB il est changé à a = 2.

Scénario 2 .Si la transaction T1 lit les données de la table A1.Si une autre transaction simultanée (T2) met à jour les données de la table A1.Les données lues par T1 sont alors différentes de celles de la table A1.Bien que T2 a mis à jour les données de la table A1.Eg si T1 lire a = 1 et T2 mis à jour a = 2.Then a! = b.

Scénario 3 .Si la transaction T1 lit les données de la table A1 avec un certain nombre de lignes. Si une autre transaction concurrente (T2) insère plus de lignes sur la table A1.Le nombre de lignes lues par T1 est différent des lignes de la table A1

Le scénario 1 s’appelle Dirty reads.

Le scénario 2 est appelé lectures non répétables.

Le scénario 3 est appelé lectures fantômes.

Le niveau d’isolement est donc l’étendue à laquelle le scénario 1, le scénario 2 et le scénario 3 peuvent être évités. Vous pouvez obtenir un niveau d’isolement complet en implémentant le locking. Cela empêche les lectures et écritures simultanées sur les mêmes données. Toutefois, cela affecte les performances. Le niveau d’isolation dépend de l’application à l’application.

ISOLATION_READ_UNCOMMITTED : Permet de lire les modifications qui n’ont pas encore été validées. Il souffre du scénario 1, scénario 2, scénario 3

ISOLATION_READ_COMMITTED : Autorise les lectures des transactions simultanées qui ont été validées. Il peut souffrir du scénario 2 et du scénario 3. D’autres transactions peuvent mettre à jour les données.

ISOLATION_REPEATABLE_READ : plusieurs lectures du même champ produiront les mêmes résultats jusqu’à ce qu’il soit modifié par lui-même. Il peut souffrir du scénario 3.Parce que d’autres transactions peuvent insérer les données

ISOLATION_SERIALIZABLE : Le scénario 1, le scénario 2, le scénario 3 ne se produit jamais.Il s’agit d’un isolement complet.Il implique un locking complet.

Vous pouvez tester en utilisant

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

Vous pouvez déboguer et voir le résultat avec différentes valeurs pour l’isolation et la propagation.

Une explication suffisante de chaque paramètre est donnée par d’autres réponses; Cependant, vous avez demandé un exemple concret, voici celui qui clarifie le but des différentes options de propagation :

Supposons que vous soyez responsable de l’implémentation d’un service d’inscription dans lequel un e-mail de confirmation est envoyé à l’utilisateur. Vous obtenez deux objects de service, l’un pour l’ enregistrement de l’utilisateur et l’autre pour l’ envoi d’ e-mails, ce dernier étant appelé dans le premier. Par exemple, quelque chose comme ceci:

 /* 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) } } 

Vous avez peut-être remarqué que le deuxième service est de type de propagation REQUIRES_NEW et que de plus, il y a de fortes chances qu’il génère une exception (serveur SMTP, courrier invalide ou autres raisons). supprimer les informations utilisateur de la firebase database ou d’autres choses; par conséquent, vous appelez le deuxième service dans une transaction distincte.

Revenons à notre exemple. Cette fois, vous êtes préoccupé par la sécurité de la firebase database, vous définissez ainsi vos classes DAO:

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

Cela signifie que chaque fois qu’un object DAO, et donc un access potentiel à la firebase database, est créé, nous devons garantir que l’appel a été effectué depuis l’un de nos services, impliquant qu’une transaction en direct doit exister; sinon une exception se produit. Par conséquent, la propagation est du type OBLIGATOIRE .

Le niveau d’isolement définit la manière dont les modifications apscopes à certains référentiels de données par une transaction affectent d’autres transactions simultanées simultanées, ainsi que la manière et le moment où les données modifiées deviennent disponibles pour d’autres transactions. Lorsque nous définissons une transaction en utilisant le framework Spring, nous sums également en mesure de configurer le niveau d’isolement de la même transaction.

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

Le niveau d’isolement READ_UNCOMMITTED indique qu’une transaction peut lire des données qui ne sont toujours pas validées par d’autres transactions.

Le niveau d’isolement READ_COMMITTED indique qu’une transaction ne peut pas lire les données qui ne sont pas encore validées par d’autres transactions.

Le niveau d’isolement REPEATABLE_READ indique que si une transaction lit plusieurs enregistrements de la firebase database, le résultat de toutes ces opérations de lecture doit toujours être le même.

Le niveau d’isolement SERIALIZABLE est le plus ressortingctif de tous les niveaux d’isolement. Les transactions sont exécutées avec un locking à tous les niveaux (lecture, intervalle et locking en écriture), elles apparaissent donc comme si elles étaient exécutées de manière sérialisée.

La propagation est la capacité de décider de la manière dont les méthodes métier doivent être encapsulées dans les transactions logiques ou physiques.

Le comportement Spring REQUIRED signifie que la même transaction sera utilisée s’il existe une transaction déjà ouverte dans le contexte d’exécution de la méthode bean en cours.

Le comportement REQUIRES_NEW signifie qu’une nouvelle transaction physique sera toujours créée par le conteneur.

Le comportement NESTED effectue des transactions Spring nestedes pour utiliser la même transaction physique, mais définit des points de sauvegarde entre les invocations nestedes afin que les transactions internes puissent également être annulées indépendamment des transactions externes.

Le comportement OBLIGATOIRE indique qu’une transaction ouverte existante doit déjà exister. Si ce n’est pas le cas, une exception sera lancée par le conteneur.

Le comportement JAMAIS indique qu’une transaction ouverte existante ne doit pas déjà exister. Si une transaction existe, une exception sera lancée par le conteneur.

Le comportement NOT_SUPPORTED s’exécutera en dehors de la scope de toute transaction. Si une transaction ouverte existe déjà, elle sera interrompue.

Le comportement SUPPORTS s’exécutera dans le cadre d’une transaction si une transaction ouverte existe déjà. S’il n’y a pas de transaction déjà ouverte, la méthode sera exécutée de toute façon mais de manière non transactionnelle.

Vous ne voulez presque jamais utiliser Read Uncommited car ce n’est pas vraiment conforme à ACID . Read Commmited est un bon sharepoint départ par défaut. Repeatable Read n’est probablement nécessaire que dans les scénarios de reporting, de cumul ou de cumul. Notez que de nombreuses bases de données, y compris postgres, ne prennent pas en charge la lecture répétable, vous devez plutôt utiliser Serializable . Serializable est utile pour les choses que vous savez devoir se produire indépendamment de toute autre chose; pense comme synchronized en Java. Serializable va de pair avec la propagation REQUIRES_NEW .

J’utilise REQUIRES pour toutes les fonctions exécutant les requêtes UPDATE ou DELETE, ainsi que les fonctions de niveau “service”. Pour les fonctions de niveau DAO qui n’exécutent que SELECT, j’utilise SUPPORTS qui participera à un TX si l’un d’entre eux est déjà démarré (c’est-à-dire appelé depuis une fonction de service).

L’isolation des transactions et la propagation des transactions bien que liées, mais sont clairement deux concepts très différents. Dans les deux cas, les parameters par défaut sont personnalisés au niveau des composants du client en utilisant la gestion des transactions déclaratives ou la gestion des transactions programmées . Les détails de chaque niveau d’isolement et des atsortingbuts de propagation peuvent être trouvés dans les liens de référence ci-dessous.

Isolation de transaction

Pour deux transactions / connexions en cours d’exécution ou plus vers une firebase database, comment et quand les modifications apscopes par les requêtes ont-elles un impact / visible pour les requêtes dans une transaction différente? Il concerne également le type de locking des enregistrements de la firebase database qui sera utilisé pour isoler les modifications de cette transaction des autres transactions et inversement. Cela est généralement implémenté par la firebase database / ressource qui participe à la transaction.

.

Propagation de transaction

Dans une application d’entreprise pour une requête / un traitement donné, de nombreux composants sont impliqués pour effectuer le travail. Certains de ces composants marquent les limites (début / fin) d’une transaction qui sera utilisée dans leurs composants respectifs et leurs sous-composants. Pour cette limite transactionnelle de composants, Transaction Propogation spécifie si le composant respectif participera ou non à la transaction et que se passe-t-il si le composant appelant a déjà ou n’a pas déjà créé / démarré une transaction. Ceci est identique aux atsortingbuts de transaction Java EE. Ceci est généralement implémenté par le gestionnaire de transactions / connexions client.

Référence:

  • Gestion des transactions de spring

  • Wiki Transaction Isolation (systèmes de firebase database)

  • Oracle sur les niveaux d’isolation de transaction

  • Atsortingbuts de transaction Java EE (propagation)

  • Spring Framework Transaction propagation

J’ai exécuté outerMethod , method_1 et method_2 avec un mode de propagation différent.

Vous trouverez ci-dessous la sortie pour différents modes de propagation.

  • Méthode externe

     @Transactional @Override public void outerMethod() { customerProfileDAO.method_1(); iWorkflowDetailDao.method_2(); } 
  • Méthode_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()) { } } } 
  • Méthode_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 – Sans transaction
      • method_1 – Propagation.MANDATORY) –
      • method_2 – Annotation de transaction uniquement
      • Output: method_1 lancera une exception qu’aucune transaction existante
      • outerMethod – Sans transaction
      • method_1 – Annotation de transaction uniquement
      • method_2 – Propagation.MANDATORY)
      • Sortie: method_2 lève une exception qu’aucune transaction existante
      • Output: method_1 va conserver l’enregistrement dans la firebase database.
      • outerMethod – Avec transaction
      • method_1 – Annotation de transaction uniquement
      • method_2 – Propagation.MANDATORY)
      • Sortie: method_2 persistera dans la firebase database.
      • Output: method_1 va conserver l’enregistrement dans la firebase database. – Voici la transaction existante principale utilisée pour les deux méthodes 1 et 2
      • outerMethod – Avec transaction
      • method_1 – Propagation.MANDATORY) –
      • method_2 – L’annotation de transaction uniquement et lève une exception
      • Sortie: aucun enregistrement ne persiste dans la firebase database, ce qui signifie que la restauration est terminée.
      • outerMethod – Avec transaction
      • method_1 – Propagation.REQUIRES_NEW)
      • method_2 – Propagation.REQUIRES_NEW) et lance une exception 1/0
      • Sortie: method_2 lève une exception pour que l’enregistrement method_2 ne soit pas conservé.
      • Output: method_1 va conserver l’enregistrement dans la firebase database.
      • Sortie: il n’y a pas de restauration pour method_1

Nous pouvons append pour cela:

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

Une transaction représente une unité de travail avec une firebase database.

Interface TransactionDefinition spring qui définit les propriétés de transaction compatibles Spring. @Transactional annotation @Transactional décrit les atsortingbuts de transaction d’une méthode ou d’une classe.

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

Propagation (Reproduction): utilise les relations inter-transactions. (analogue à la communication inter-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. | +-------+---------------------------+------------------------------------------------------------------------------------------------------+ 

Isolation: L’isolement est l’une des propriétés ACID (Atomicity, Consistency, Isolation, Durability) des transactions de firebase database. L’isolement détermine la manière dont l’intégrité de la transaction est visible pour les autres utilisateurs et systèmes. Il utilise pour le locking des ressources, c.-à-d. Le contrôle de la concurrence, assurez-vous qu’une seule transaction peut accéder à la ressource à un point donné.

Perception de locking: le niveau d’isolement détermine la durée des verrous.

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

Lire la perception: les trois types de problèmes majeurs suivants se produisent:

  • Dirty reads : lit les données non validées d’un autre tx (transaction).
  • Lectures non répétables : lit les UPDATES validées d’un autre tx.
  • Le fantôme lit : lit les INSERTS et / ou DELETES d’un autre tx

Niveaux d’isolement avec différents types de lectures:

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

pour des exemples

Vous pouvez utiliser comme ceci:

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

Vous pouvez aussi utiliser cette chose:

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