2010-05-26 19 views
7

Descubrí un comportamiento realmente extraño en un caso de uso relativamente simple, probablemente no puedo entenderlo debido a los conocimientos no profundos de la naturaleza primavera @Transactional, pero esto es Bastante interesante.Extraño comportamiento de la compatibilidad de la transacción de primavera para JPA + Hibernate + @ Anotación transaccional

me han dao usuario simple que se extiende clase JpaDaoSupport primavera y contiene estándar de guardar método:

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

Si estaba trabajando bien hasta que haya añadir nuevo método para una misma clase: Usuario getSuperUser(), este método debe devolver usuario con isAdmin == verdadero, y si no hay superusuario en db, el método debe crear uno. Eso es lo que estaba buscando como:

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; 
} 

Como se ve el código es extraño hacia adelante y yo estaba muy confundido cuando descubrió que no hay ninguna transacción se crea y se ha comprometido en la invocación del método Save (admin) y ningún nuevo usuario no era' t creado a pesar de la anotación @Transactional.

En resultado tenemos situación: cuando el método save() invoca desde el exterior de la clase UserDAO - Anotación @Transactional contada y el usuario creado con éxito, pero si save() invoca desde el otro método de la misma clase dao - @Transactional anotación ignorada.

Aquí cómo me cambié el método save() para forzarlo crea siempre una transacción.

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; 
} 

Como ve, invoco manualmente begin y commit. ¿Algunas ideas?

Respuesta

5

Creo que las transacciones declarativas con anotación se implementan sobre la base de proxies.

Si accede a su DAO a través del proxy dinámico, verifica si hay una anotación y la envuelve con una transacción.

Si llama a su clase desde dentro de la clase, no hay forma de interceptar esta llamada.

Para evitar el problema, puede marcar el método createSuperuser con una anotación también.

+1

gracias por una explicación tan profunda, dicho sea de paso, ¿y si activare aspectj como sé que aspectj es una alternativa al proxy dinámico? no tiene sentido marcar createSuperuser como transaccional, siempre que aún lo invoque desde dentro de getSuperUser en la misma clase, pero sí - cuando marque getSuperUser transaccional - todo funciona bien – abovesun

7
  1. @Transactional se tiene en cuenta solo para llamadas desde el exterior del objeto. Para llamadas internas, no lo es. Para solucionar esto, solo agregue @Transactional a su punto de entrada.
  2. No use @Transactional para su DAO; utilícelo en las clases de servicio.
+0

¡Gracias, volveré a leer el doc. De primavera sobre este tema! – abovesun

+0

No siempre tiene sentido marcar el método de Servicio como transaccional, tengo 2 proveedores persistentes enchufables en mi aplicación, uno es JPA + Hibernate, otro es MongoDB + DAO personalizado, y probablemente será un tercero, compatible con Cassandra. Entonces @Transactional solo tiene sentido en caso de que JPA esté activo. – abovesun

+0

no del todo - el comportamiento transaccional es un concepto de mecanismo de persistencia cruzada. Es cierto que JPA exige transacciones, mientras que otros mecanismos no, pero las transacciones aún existen. Y con un administrador de transacciones adecuado, tiene sentido usar la anotación en el nivel de servicio. – Bozho

0

Su problema está relacionado con las limitaciones de Spring AOP. El answer de Bozho es bueno y debería considerar refactorizar su código para respaldar sus consejos.

¡Pero si quiere que su transacción funcione sin cambiar ningún código, es posible!

Spring AOP es la opción predeterminada en las tecnologías de aspectos de Spring. Pero con alguna configuración y agregando el tejido AspectJ, funcionará, ya que AspectJ es una tecnología mucho más poderosa que permite puntos entre dos métodos en la misma clase.

+0

gracias Espen, usted respondió en mi comentar por su respuesta anterior – abovesun

Cuestiones relacionadas