web-dev-qa-db-fra.com

Démarrage d'une nouvelle transaction dans Spring Bean

Nous avons:

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

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

Lorsque go () s'exécute, nous commençons une nouvelle transaction qui valide/annule lorsque la méthode est terminée - c'est très bien.

Supposons maintenant 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 démarre pas une nouvelle transaction. Je pense que c'est parce que Spring AOP fonctionne au niveau de l'interface (MyInterface) et n'intercepte aucun appel aux méthodes MyClass. Est-ce correct?

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


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

@Autowired
MyInterface impl;

impl.go();
43
Marcus Leon

D'après la référence Spring 2.5:

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

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

Aussi,

En mode proxy (qui est la valeur par défaut), seuls les appels de méthode "externes" entrant via le proxy 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!

Ainsi, même si vous créez votre méthode public, l'appeler à partir d'une méthode de la même classe ne démarrera pas une nouvelle transaction.

Vous pouvez utiliser le mode aspectj dans les paramètres de transaction afin que le code associé à 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 possible de procéder consiste à récupérer le proxy de printemps de la classe dans la classe elle-même et à appeler des méthodes dessus plutôt que 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
    }

}
76
Abhinav Sarkar

@Transactional ne sera remarqué que s'il est sur une méthode public, en raison du fonctionnement de Spring AOP.

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

TransactionTemplate txTemplate = new TransactionTemplate(txManager);                
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
txTemplate.execute(new TransactionCallback<Object>() {
    public Object doInTransaction(TransactionStatus status) {
        // do stuff
    }
});
42
skaffman

En bref, vous devez appeler la méthode via 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 "auto". Au printemps, ce n'est pas simple. 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 faire fonctionner la transaction "REQUIRES_NEW".

5
wmlynarski