54 votes

Démarrage d'une nouvelle transaction dans un haricot de Spring

Nous avons :

@Transactional(propagation = Propagation.REQUIRED)
public class MyClass implementes MyInterface { ...

MonInterface a une seule méthode : go() .

Lorsque go() s'exécute, nous démarrons une nouvelle transaction qui s'engage/se retire lorsque la méthode est terminée - c'est parfait.

Maintenant disons que dans go() nous appelons une méthode privée dans MyClass qui a @Transactional(propagation = Propagation.REQUIRES_NEW . Il semble que Spring "ignore" l'annotation REQUIRES_NEW et ne lance pas de nouvelle transaction. Je pense que c'est parce que Spring AOP opère au niveau de l'interface (MyInterface) et n'intercepte aucun appel aux méthodes de MyClass. Est-ce correct ?

Existe-t-il un moyen de lancer une nouvelle transaction au sein de la transaction go() ? La seule façon d'appeler un autre Un bean géré par Spring dont les transactions sont configurées comme REQUIRES_NEW ?


Mise à jour : Ajoutant que lorsque les clients exécutent go() ils le font via une référence à l'interface, et non à la classe :

@Autowired
MyInterface impl;

impl.go();

93voto

Abhinav Sarkar Points 10904

De la référence de printemps 2.5 :

Lors de l'utilisation de proxies, le @Transactional ne doit être appliquée qu'aux avec une visibilité publique. Si vous annotez des méthodes protégées, privées ou visibles par paquet avec l'annotation @Transactional l'annotation, aucune erreur ne sera mais la méthode annotée ne présentera pas les paramètres transactionnels configurés. configurés.

Donc Spring ignore @Transactional sur les méthodes non publiques.

Aussi,

En mode proxy (qui est le mode par défaut), seuls les appels de méthodes "externes" qui arrivent par le proxy seront interceptés. Cela signifie que l'"auto-invocation", c'est-à-dire qu'une méthode de l'objet cible appelle une autre méthode de l'objet cible. cible, ne conduira pas à une transaction réelle au moment de l'exécution même si la méthode invoquée invoquée est marquée avec @Transactional !

Donc même si vous faites votre méthode public l'appeler à partir d'une méthode de la même classe ne lancera pas une nouvelle transaction.

Vous pouvez utiliser aspectj dans les paramètres de transaction afin que le code lié à la transaction soit tissé dans la classe et qu'aucun proxy ne soit créé au moment de l'exécution.

Voir le document de référence pour plus de détails.

Une autre façon de procéder est de récupérer le proxy de la classe dans la classe elle-même et d'appeler des méthodes sur ce proxy plutôt que sur la classe elle-même. this :

@Service
@Transactional(propagation = Propagation.REQUIRED)
public class SomeService {

    @Autowired
    private ApplicationContext applicationContext;

    private SomeService  getSpringProxy() {
        return applicationContext.getBean(this.getClass());
    }

    private void doSomeAndThenMore() {
        // instead of
        // this.doSometingPublicly();
        // do the following to run in transaction
        getSpringProxy().doSometingPublicly();
    }

    public void doSometingPublicly() {
        //do some transactional stuff here
    }

}

56voto

skaffman Points 197885

@Transactional ne sera remarqué que s'il se trouve sur une public en raison du mode de fonctionnement de Spring AOP.

Cependant, vous pouvez démarrer par programme une nouvelle transaction si vous le souhaitez, en utilisant TransactionTemplate par exemple

TransactionTemplate txTemplate = new TransactionTemplate(txManager);                
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
txTemplate.execute(status -> {
        // do stuff
});

0 votes

Mais même si @Transactional est sur une méthode publique de MyClass, il semble toujours que Spring ne le prenne pas en compte. sauf si la méthode est définie dans l'interface - correct ?

0 votes

Pour ajouter, c'est parce que le client qui exécute la méthode go() en question a une référence à l'interface, pas à la classe.

4 votes

@Marcus : En quelque sorte. Si MyClass implémente une interface, Spring n'utilisera que cette interface pour générer le proxy transactionnel, et ignorera même les méthodes publiques qui ne font pas partie de l'interface. Cependant, si MyClass n'implémente aucune interface, alors toutes les méthodes publiques seront utilisées.

10voto

wmlynarski Points 326

En bref, vous devez appeler la méthode par le biais d'un proxy pour obtenir le comportement transactionnel. Il est possible d'appeler un "REQUIRES_NEW" dans le même bean, comme demandé dans la question. Pour ce faire, vous devez faire une référence "self". Dans Spring, ce n'est pas directement possible. Vous devez l'injecter avec l'annotation @Resource.

@Service("someService")
public class ServieImpl implements Service {

   @Resource(name = "someService")
   Service selfReference;

   @Transactional
   public void firstMethod() {
       selfReference.secondMethod();
   }

   @Transactional(propagation = Propagation.REQUIRES_NEW) 
   public void secondMethod() {    
         //do in new transaction
   }

} 

L'invocation dans firstMethod appelle le proxy et non "this", ce qui devrait permettre à la transaction "REQUIRES_NEW" de fonctionner.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X