2010-08-16 19 views
11

Estoy usando eventos de aplicaciones Spring en mi capa de servicio para notificar al sistema en general cuando ocurren eventos específicos. El problema que tengo es que los eventos se disparan dentro de un contexto transaccional (estoy usando la anotación de Spring @Transactional). El peligro es que dispare un evento y luego la transacción falla en la confirmación (puede ocurrir al usar, por ejemplo, Hibernate). En este escenario, los oyentes del evento asumirán algún estado que luego se revertirá después de que se hayan ejecutado. El peligro aquí es que, por ejemplo, puedo haber enviado un correo electrónico para confirmar el registro de un usuario en un sitio web cuando, de hecho, su cuenta de usuario no se creó realmente.Anotaciones de transacciones de primavera: Ejecutar en caso de éxito

¿Existe alguna manera, preservando el uso de anotaciones, de marcar en algún lugar un evento para que se active después de que se compromete la transacción? En cierto modo, parece algo similar al uso de SwingUtilities.invokeLater(Runnable runnable) al hacer la programación de la GUI en Java Swing. Quiero decir, ejecutar este código poco después, una vez que la transacción actual se haya confirmado correctamente.

¿Alguna idea?

Gracias,

Andrew

+0

Todo esto es manejado de forma nativa en la primavera de ahora a través de anotaciones. Ver https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2https://spring.io/blog/2015/02/11/better -application-events-in-spring-framework-4-2 específicamente la sección sobre la nueva anotación @TransactionalEventListener. – PaulNUK

Respuesta

2

la forma correcta de resolver su problema con el correo electrónico de confirmación es probablemente usar un transacciones distribuidas con un recurso JMS participar en ellos.

Sin embargo, como una solución rápida y sucia-, puede tratar de crear una envoltura alrededor de un PlatformTransactionManager que ejecutará Runnable s inscrito en algún ThreadLocal almacenamiento después de cometer exitosa (y sacarlo de ThreadLocal después de recuperar). En realidad, AbstractPlatformTransactionManager tiene exactamente este tipo de lógica con TransactionSynchronization s, pero está enterrada demasiado profundamente en sus detalles de implementación.

+0

He ido con la solución rápida y sucia por ahora. Creé un mecanismo basado en ThreadLocal que asocia una cola de objetos Runnable con un objeto TransactionStatus. En commit() o rollback() de un TransactionStatus, ejecuto los objetos ejecutables o los descarto. Funciona bastante bien. – DrewEaster

+0

¿Podría proporcionar un fragmento de código? – Oliv

+0

No diría que las transacciones distribuidas son la solución _proper_ correcta. La transacción distribuida fallaría toda la transacción si simplemente hubiera fallado el envío del correo. Pero el comportamiento deseado aquí es que el correo depende de la transacción DB y la transacción DB no depende del correo. – yair

4

Puede hacer uso de TransactionSynchronizationManager sin necesidad de hackear PlatformTransactionManager.

Nota: TransactionAware es una interfaz de marcador que indica que un ApplicationListener desea recibir un evento después de que la transacción se haya realizado correctamente.

public class TransactionAwareApplicationEventMulticaster extends SimpleApplicationEventMulticaster { 

    @Override 
    public void multicastEvent(ApplicationEvent event) { 
     for (ApplicationListener listener : getApplicationListeners(event)) { 
      if ((listener instanceof TransactionAware) && TransactionSynchronizationManager.isSynchronizationActive()) { 
       TransactionSynchronizationManager.registerSynchronization(
        new EventTransactionSynchronization(listener, event)); 
      } 
      else { 
       notifyEvent(listener, event); 
      } 
     } 
    } 

    void notifyEvent(final ApplicationListener listener, final ApplicationEvent event) { 
      Executor executor = getTaskExecutor(); 
      if (executor != null) { 
       executor.execute(new Runnable() { 
        public void run() { 
         listener.onApplicationEvent(event); 
        } 
       }); 
      } 
      else { 
       listener.onApplicationEvent(event); 
      } 
    } 

    class EventTransactionSynchronization extends TransactionSynchronizationAdapter { 
     private final ApplicationListener listener; 
     private final ApplicationEvent event; 

     EventTransactionSynchronization(ApplicationListener listener, ApplicationEvent event) { 
      this.listener = listener; 
      this.event = event; 
     } 

     @Override 
     public void afterCompletion(int status) { 
      if ((phase == TransactionPhase.AFTER_SUCCESS) 
        && (status == TransactionSynchronization.STATUS_COMMITTED)) { 
       notifyEvent(listener, event); 
      } 
     } 
    } 
} 
10

Esto funciona para mí:

TransactionSynchronizationManager.registerSynchronization(
    new TransactionSynchronizationAdapter() { 
     @Override 
     public void afterCommit() { 
      // things to do when commited 
     } 
     // other methods to override are available too 
    }); 
+0

¡Agradable! La mejor respuesta en relación con la pregunta IMO. Esto es más parecido a SwingUtilities.invokeLater. –

Cuestiones relacionadas