2010-05-26 4 views
7

J'ai découvert un comportement vraiment étrange sur un cas d'utilisation relativement simple, probablement je ne peux pas le comprendre en raison de connaissances pas profondes du printemps @Transactional nature, mais c'est plutôt interessant.Strange befaviour de soutien de transaction de printemps pour JPA + Hibernate + @ annotation transactionnelle

J'ai simples dao utilisateur qui étend la classe ressort JpaDaoSupport et contient la méthode standard d'enregistrement:

@Transactional 
public User save(User user) { 
    getJpaTemplate().persist(user); 
    return user; 
} 

Si fonctionnait très bien jusqu'à ce que j'ai ajouter une nouvelle méthode pour une même classe: User getSuperUser(), cette méthode devrait renvoie l'utilisateur avec isAdmin == true, et s'il n'y a pas de super utilisateur dans db, la méthode devrait en créer un. Voilà comment il a été Ressemblant:

public User createSuperUser() { 
    User admin = null; 

    try { 
     admin = (User) getJpaTemplate().execute(new JpaCallback() { 
      public Object doInJpa(EntityManager em) throws PersistenceException { 
       return em.createQuery("select u from UserImpl u where u.admin = true").getSingleResult(); 
      } 
     }); 
    } catch (EmptyResultDataAccessException ex) { 
     User admin = new User('login', 'password'); 
     admin.setAdmin(true); 
     save(admin); // THIS IS THE POINT WHERE STRANGE THING COMING OUT 
    } 

    return admin; 
} 

Comme vous voyez le code est étrange méthode avant et je suis très confus quand a découvert qu'aucune transaction a été créée et engagée sur l'invocation de sauvegarde (admin) et aucun nouvel utilisateur n » était pas t réellement créé malgré l'annotation @Transactional. En résultat, nous avons la situation: lorsque la méthode save() appelle de l'extérieur de la classe UserDAO - Annotation @Transactional comptée et l'utilisateur créé avec succès, mais si save() invoque de l'intérieur d'une autre méthode de la même classe dao - @Transactional annotation ignorée.

Voici comment j'ai changé la méthode save() pour la forcer à toujours créer une transaction.

public User save(User user) { 
    getJpaTemplate().execute(new JpaCallback() { 
     public Object doInJpa(EntityManager em) throws PersistenceException { 
      em.getTransaction().begin(); 
      em.persist(user); 
      em.getTransaction().commit(); 
      return null; 
     } 
    }); 
    return user; 
} 

Comme vous le voyez, j'appelle manuellement begin et commit. Des idées?

Répondre

5

Je pense que les transactions déclaratives avec annotation sont implémentées à la base des proxies.

Si vous accédez à votre DAO via le proxy dynamique, il vérifie s'il existe une annotation et l'enveloppe avec une transaction.

Si vous appelez votre classe depuis l'intérieur de la classe, il n'y a aucun moyen d'intercepter cet appel. Pour éviter le problème, vous pouvez également marquer la méthode createSuperuser avec une annotation.

+1

merci pour une telle explication profonde, à propos, que se passe-t-il si je vais activer aspectj comme je sais que l'aspectj est une alternative au proxy dynamique. cela n'a aucun sens de marquer createSuperuser comme transactionnel, tant que je l'invoque toujours de l'intérieur de getSuperUser dans la même classe, mais oui - quand je marque getSuperUser transactionnel - tout fonctionne bien – abovesun

7
  1. @Transactional est pris en compte uniquement pour les appels provenant de l'extérieur de l'objet. Pour les appels internes, ce n'est pas le cas. Pour cela, il suffit d'ajouter @Transactional à votre point d'entrée.
  2. N'utilisez pas @Transactional pour votre DAO - utilisez-le plutôt sur les classes Service.
+0

Merci, relira spring doc à ce sujet! – abovesun

+0

Il n'est pas toujours logique de marquer la méthode de service comme transactionnelle, j'ai 2 fournisseurs persistants enfichables dans mon application, un JPA + Hibernate, l'autre est MongoDB + DAO personnalisé, et sera probablement un troisième - support Cassandra. Donc @Transactional n'a de sens que si JPA est active. – abovesun

+0

pas tout à fait - le comportement transactionnel est un concept de mécanisme de persistance croisée. Certes, JPA exige des transactions, tandis que d'autres ne le font pas, mais les transactions existent toujours. Et avec un gestionnaire de transactions approprié, il est logique d'utiliser l'annotation au niveau du service. – Bozho

0

Votre problème est lié aux limitations de Spring AOP. Le answer de Bozho en est un bon et vous devriez envisager de refactoriser votre code pour soutenir ses conseils. Mais si vous voulez que votre transaction fonctionne sans changer de code, c'est possible! Spring AOP est le choix par défaut dans les technologies des aspects de printemps.

Mais avec une certaine configuration et en ajoutant le tissage AspectJ, cela fonctionnera, car AspectJ est une technologie beaucoup plus puissante qui permet des coupures de point entre deux méthodes dans la même classe.

+0

merci Espen, vous avez répondu sur mon commentaire pour précédent à votre réponse – abovesun

Questions connexes