2010-08-06 4 views
67

Je suis nouveau à Spring Transaction. Quelque chose que j'ai trouvé vraiment étrange, probablement je l'ai bien compris. Je voulais avoir un niveau de méthode transactionnel et j'ai une méthode d'appelant dans la même classe et il semble que ça n'aime pas cela, il faut l'appeler depuis la classe séparée. Je ne comprends pas comment c'est possible. Si quelqu'un a une idée de la façon de résoudre ce problème, j'apprécierais beaucoup. Je voudrais utiliser la même classe pour appeler la méthode transactionnelle annotée.Printemps méthode @Transaction appel par la méthode dans la même classe, ne fonctionne pas?

Voici le code:

public class UserService { 

    @Transactional 
    public boolean addUser(String userName, String password) { 
     try { 
      // call DAO layer and adds to database. 
     } catch (Throwable e) { 
      TransactionAspectSupport.currentTransactionStatus() 
        .setRollbackOnly(); 

     } 
    } 

    public boolean addUsers(List<User> users) { 
     for (User user : users) { 
      addUser(user.getUserName, user.getPassword); 
     } 
    } 
} 

Répondre

70

Il est une limitation avec Spring AOP. (objets dynamiques et CGLIB)

Si vous configurez Spring pour utiliser AspectJ pour gérer les transactions, votre code fonctionnera.

La solution simple et probablement la meilleure est de refactoriser votre code. Par exemple, une classe qui gère les utilisateurs et une qui traite chaque utilisateur. Ensuite, la gestion des transactions par défaut avec Spring AOP fonctionnera.

Conseils de configuration pour les transactions de manutention avec AspectJ

Pour activer Spring utiliser AspectJ pour les transactions, vous devez définir le mode AspectJ:

<tx:annotation-driven mode="aspectj"/> 

Si vous utilisez Spring avec une ancienne version de 3.0, vous devez également ajouter ceci à votre configuration de ressort:

<bean class="org.springframework.transaction.aspectj 
     .AnnotationTransactionAspect" factory-method="aspectOf"> 
    <property name="transactionManager" ref="transactionManager" /> 
</bean> 
+0

Nous vous remercions de l'information. J'ai refacturé le code pour l'instant, mais pourriez-vous s'il vous plaît m'envoyer un exemple en utilisant AspectJ ou me fournir quelques liens utiles. Merci d'avance. Mike. – Mike

+0

Ajout d'une configuration AspectJ spécifique à la transaction dans ma réponse. J'espère que ça aide. – Espen

+1

Merci Espen pour votre aide. Ça marche!! – Mike

42

Le problème ici est , que les proxies AOP de Spring ne s'étendent pas mais enveloppent votre instance de service pour intercepter les appels. Cela a pour effet que tout appel à "this" depuis votre instance de service est directement appelé sur cette instance et ne peut pas être intercepté par le proxy de wrapping (le proxy n'est même pas au courant d'un tel appel). Une solution est déjà mentionnée. Un autre avantage serait de simplement faire en sorte que Spring injecte une instance du service dans le service lui-même, et appelle votre méthode sur l'instance injectée, qui sera le proxy qui gère vos transactions. Mais sachez, que cela peut avoir de mauvais effets secondaires aussi, si votre grain de service n'est pas un singleton:

<bean id="userService" class="your.package.UserService"> 
    <property name="self" ref="userService" /> 
    ... 
</bean> 

public class UserService { 
    private UserService self; 

    public void setSelf(UserService self) { 
     this.self = self; 
    } 

    @Transactional 
    public boolean addUser(String userName, String password) { 
     try { 
     // call DAO layer and adds to database. 
     } catch (Throwable e) { 
      TransactionAspectSupport.currentTransactionStatus() 
       .setRollbackOnly(); 

     } 
    } 

    public boolean addUsers(List<User> users) { 
     for (User user : users) { 
      self.addUser(user.getUserName, user.getPassword); 
     } 
    } 
} 
+2

Si vous choisissez d'emprunter cette route (que ce soit un bon design ou non est une autre affaire) et n'utilisez pas l'injection constructeur, assurez-vous de question] (http://stackoverflow.com/questions/5152686/self-injection-with-spring) – Jeshurun

0

Vous pouvez autowired BeanFactory dans la même classe et faire un

getBean(YourClazz.class)

Il Proxyptera automatiquement votre classe et prendra en compte votre annotation @Transactional ou autre.

+2

Il est considéré comme une mauvaise pratique, même en injectant le bean de manière récursive en lui-même, mieux vaut utiliser getBean (clazz) le couplage et la forte dépendance sur les classes Spring ApplicationContext à l'intérieur de votre code.En outre, l'obtention d'un bean par classe peut ne pas fonctionner dans le cas d'un enveloppement printanier du bean (la classe peut être modifiée). –

5

Ceci est ma solution pour l'auto-invocation.

private SBMWSBL self; 
@Autowired private ApplicationContext applicationContext; 

@PostConstruct 
public void postContruct(){ 
    self =applicationContext.getBean(SBMWSBL.class); 
} 
Questions connexes