Spring - @Transactional - Que se passe-t-il en arrière-plan?


334

Je veux savoir ce qui se passe réellement lorsque vous annotez une méthode avec @Transactional? Bien sûr, je sais que Spring encapsulera cette méthode dans une transaction.

Mais, j'ai les doutes suivants:

  1. J'ai entendu dire que Spring crée une classe proxy ? Quelqu'un peut-il expliquer cela plus en profondeur ? Qu'est-ce qui réside réellement dans cette classe proxy? Qu'arrive-t-il à la classe actuelle? Et comment puis-je voir la classe proxy créée par Spring
  2. J'ai également lu dans Spring docs que:

Remarque: Puisque ce mécanisme est basé sur des mandataires, seuls les appels de méthode «externes» entrant via le mandataire seront interceptés . Cela signifie que «l'auto-invocation», c'est-à-dire une méthode dans l'objet cible appelant une autre méthode de l'objet cible, ne conduira pas à une transaction réelle au moment de l'exécution même si la méthode invoquée est marquée avec @Transactional!

Source: http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

Pourquoi seuls les appels de méthode externes seront sous Transaction et non les méthodes d'auto-invocation?


2
La discussion pertinente est ici: stackoverflow.com/questions/3120143/…
dma_k

Réponses:


255

C'est un gros sujet. Le document de référence Spring lui consacre plusieurs chapitres. Je recommande de lire celles sur la programmation et les transactions orientées aspect , car le support déclaratif des transactions de Spring utilise AOP à sa fondation.

Mais à un niveau très élevé, Spring crée des proxys pour les classes qui déclarent @Transactional sur la classe elle-même ou sur les membres. Le proxy est principalement invisible au moment de l'exécution. Il permet à Spring d'injecter des comportements avant, après ou autour des appels de méthode dans l'objet mandaté. La gestion des transactions n'est qu'un exemple des comportements pouvant être connectés. Les contrôles de sécurité en sont un autre. Et vous pouvez également fournir le vôtre pour des choses comme la journalisation. Ainsi, lorsque vous annotez une méthode avec @Transactional , Spring crée dynamiquement un proxy qui implémente les mêmes interfaces que la classe que vous annotez. Et lorsque les clients effectuent des appels dans votre objet, les appels sont interceptés et les comportements injectés via le mécanisme de proxy.

Soit dit en passant, les transactions dans EJB fonctionnent de la même manière.

Comme vous l'avez observé, à travers, le mécanisme de proxy ne fonctionne que lorsque les appels proviennent d'un objet externe. Lorsque vous effectuez un appel interne au sein de l'objet, vous effectuez réellement un appel via la référence " this ", qui contourne le proxy. Il existe cependant des moyens de contourner ce problème. J'explique une approche dans ce billet de forum dans laquelle j'utilise un BeanFactoryPostProcessor pour injecter une instance du proxy dans des classes "auto-référencées" lors de l'exécution. J'enregistre cette référence dans une variable membre appelée " moi ". Ensuite, si je dois effectuer des appels internes qui nécessitent une modification de l'état de transaction du thread, je dirige l'appel via le proxy (par exemple, " me.someMethod ()".) Le message du forum explique plus en détail. Notez que le code BeanFactoryPostProcessor serait un peu différent maintenant, car il a été réécrit dans le calendrier Spring 1.x. Mais j'espère qu'il vous donne une idée. J'ai une version mise à jour qui Je pourrais probablement mettre à disposition.


4
>> Le proxy est pratiquement invisible à l'exécution Oh !! Je suis curieux de les voir :) Reste .. ta réponse était très complète. C'est la deuxième fois que vous m'aidez ... Merci pour toute l'aide.
Peakit

17
Aucun problème. Vous pouvez voir le code proxy si vous faites un pas avec un débogueur. C'est probablement le moyen le plus simple. Il n'y a pas de magie; ce ne sont que des classes dans les packages Spring.
Rob H

Et si la méthode qui a l'annotation @Transaction implémente une interface, le ressort utilisera l'API proxy dynamique pour injecter la transactionnalisation et ne pas utiliser de proxy. Je préfère que mes classes transactionnelles implémentent des interfaces dans tous les cas.
Michael Wiles

1
J'ai aussi trouvé le schéma «moi» (en utilisant un câblage explicite pour le faire comme il convient à ma façon de penser), mais je pense que si vous le faites de cette façon, vous feriez probablement mieux de refactoriser afin de ne pas devoir. Mais oui, cela peut parfois être très gênant!
Donal Fellows

2
2019: Comme cette réponse vieillit, l'article de forum référé n'est plus disponible, ce qui décrirait le cas où vous devez effectuer un appel interne au sein de l'objet sans contourner le proxy, en utilisantBeanFactoryPostProcessor . Cependant, il existe (à mon avis) une méthode très similaire décrite dans cette réponse: stackoverflow.com/a/11277899/3667003 ... et d'autres solutions dans tout le thread également.
Z3d4s

196

Lorsque Spring charge vos définitions de bean et a été configuré pour rechercher des @Transactionalannotations, il crée ces objets proxy autour de votre bean réel . Ces objets proxy sont des instances de classes qui sont générées automatiquement au moment de l'exécution. Le comportement par défaut de ces objets proxy lorsqu'une méthode est invoquée consiste simplement à invoquer la même méthode sur le bean "cible" (c'est-à-dire votre bean).

Cependant, les mandataires peuvent également être fournis avec des intercepteurs, et lorsqu'ils sont présents, ces intercepteurs seront invoqués par le proxy avant d'appeler la méthode de votre bean cible. Pour les beans cible annotés de @Transactional, Spring créera un TransactionInterceptoret le transmettra à l'objet proxy généré. Ainsi, lorsque vous appelez la méthode à partir du code client, vous appelez la méthode sur l'objet proxy, qui appelle d'abord le TransactionInterceptor(qui commence une transaction), qui à son tour invoque la méthode sur votre bean cible. Une fois l'invocation terminée, la commande TransactionInterceptorvalide / annule la transaction. Il est transparent pour le code client.

En ce qui concerne la "méthode externe", si votre bean invoque une de ses propres méthodes, il ne le fera pas via le proxy. Rappelez-vous, Spring enveloppe votre bean dans le proxy, votre bean n'en a aucune connaissance. Seuls les appels provenant de "l'extérieur" de votre bean passent par le proxy.

Est ce que ça aide?


36
> Souvenez-vous, Spring enveloppe votre bean dans le proxy, votre bean n'en a aucune connaissance Ceci dit tout. Quelle excellente réponse. Merci pour ton aide.
Peakit

Grande explication, pour le proxy et les intercepteurs. Maintenant, je comprends que Spring implémente un objet proxy pour intercepter les appels vers un bean cible. Je vous remercie!
dharag

Je pense que vous essayez de décrire cette image de la documentation de Spring et que cette image m'aide beaucoup: docs.spring.io/spring/docs/4.2.x/spring-framework-reference/…
WesternGun

44

En tant que personne visuelle, j'aime peser avec un diagramme de séquence du modèle proxy. Si vous ne savez pas lire les flèches, je lis la première comme ceci: Clientexécute Proxy.method().

  1. Le client appelle une méthode sur la cible de son point de vue et est silencieusement intercepté par le proxy
  2. Si un aspect avant est défini, le proxy l'exécutera
  3. Ensuite, la méthode réelle (cible) est exécutée
  4. Après-retour et après-lancement sont des aspects facultatifs qui sont exécutés après le retour de la méthode et / ou si la méthode lève une exception
  5. Après cela, le proxy exécute l'aspect après (s'il est défini)
  6. Enfin, le proxy revient au client appelant

Diagramme de séquence de modèle de proxy (J'ai été autorisé à poster la photo à condition d'en mentionner les origines. Auteur: Noel Vaes, site internet: www.noelvaes.eu)


27

La réponse la plus simple est:

Quelle que soit la méthode, vous déclarez @Transactionalla limite de début et de fin de la transaction à la fin de la méthode.

Si vous utilisez l'appel JPA, tous les validations sont avec dans cette limite de transaction .

Disons que vous enregistrez entité1, entité2 et entité3. Maintenant , tout en économisant entity3 une exception se produit , alors que enitiy1 et entité2 est dans la même transaction afin entity1 et entité2 seront rollback avec entity3.

Transaction:

  1. entity1.save
  2. entity2.save
  3. entity3.save

Toute exception entraînera la restauration de toutes les transactions JPA avec DB. Les transactions JPA internes sont utilisées par Spring.


2
"Une exception A̶n̶y̶ entraînera la restauration de toutes les transactions JPA avec DB." Remarque Seul RuntimeException entraîne une restauration. Les exceptions vérifiées n'entraîneront pas de restauration.
Arjun

2

Il est peut-être tard, mais je suis tombé sur quelque chose qui explique bien votre préoccupation liée au proxy (seuls les appels de méthode «externes» entrant via le proxy seront interceptés).

Par exemple, vous avez une classe qui ressemble à ceci

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
    }

    public void doSomethingSmall(int x){
        System.out.println("I also do something small but with an int");    
  }
}

et vous avez un aspect qui ressemble à ceci:

@Component
@Aspect
public class CrossCuttingConcern {

    @Before("execution(* com.intertech.CoreBusinessSubordinate.*(..))")
    public void doCrossCutStuff(){
        System.out.println("Doing the cross cutting concern now");
    }
}

Lorsque vous l'exécutez comme ceci:

 @Service
public class CoreBusinessKickOff {

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

    public void kickOff() {
       System.out.println("I do something big");
       subordinate.doSomethingBig();
       subordinate.doSomethingSmall(4);
   }

}

Résultats de l'appel de kickOff au-dessus du code donné ci-dessus.

I do something big
Doing the cross cutting concern now
I did something small
Doing the cross cutting concern now
I also do something small but with an int

mais quand vous changez votre code en

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
        doSomethingSmall(4);
    }

    public void doSomethingSmall(int x){
       System.out.println("I also do something small but with an int");    
   }
}


public void kickOff() {
  System.out.println("I do something big");
   subordinate.doSomethingBig();
   //subordinate.doSomethingSmall(4);
}

Vous voyez, la méthode appelle en interne une autre méthode afin qu'elle ne soit pas interceptée et la sortie ressemblerait à ceci:

I do something big
Doing the cross cutting concern now
I did something small
I also do something small but with an int

Vous pouvez contourner cela en faisant cela

public void doSomethingBig() {
    System.out.println("I did something small");
    //doSomethingSmall(4);
    ((CoreBusinessSubordinate) AopContext.currentProxy()).doSomethingSmall(4);
}

Extraits de code extraits de: https://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/


1

Toutes les réponses existantes sont correctes, mais je ne pense pas pouvoir donner seulement ce sujet complexe.

Pour une explication complète et pratique, vous voudrez peut-être consulter ce guide Spring @Transactional In-Depth , qui fait de son mieux pour couvrir la gestion des transactions en environ 4000 mots simples, avec de nombreux exemples de code.


C'était génial ...
Alpit Anand Il y a
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.