2010-08-16 14 views
11

Sto utilizzando gli eventi di applicazione Spring all'interno del mio livello di servizio per notificare il sistema più ampio quando si verificano eventi specifici. Il problema che ho è che gli eventi vengono lanciati all'interno di un contesto transazionale (sto usando l'annotazione di Spring @Transactional). Il pericolo è che spari un evento e quindi la transazione non riesce sul commit (può accadere quando si usa, per esempio, Hibernate). In questo scenario, gli ascoltatori di eventi assumeranno uno stato che verrà poi ripristinato dopo l'esecuzione. Il pericolo qui è che, ad esempio, potrei aver inviato un'email per confermare la registrazione di un utente su un sito Web quando, in realtà, il loro account utente non è stato effettivamente creato.Annotazioni transazioni a molla - Esegui in caso di successo

C'è un modo, preservando l'uso delle annotazioni, in qualche punto per segnalare un evento da attivare dopo il commit della transazione? Sembra un po 'analogo all'utilizzo di SwingUtilities.invokeLater(Runnable runnable) durante la programmazione GUI in Java Swing. Voglio dire, eseguire questo bit di codice in seguito una volta che la transazione corrente viene eseguita correttamente.

Qualche idea?

Grazie,

Andrew

+0

Questo è tutto gestito in modo nativo in primavera la società tramite annotazioni. Vedere 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 in particolare la sezione sulla nuova annotazione @TransactionalEventListener. – PaulNUK

risposta

2

il modo corretto di risolvere il problema con e-mail di conferma è probabilmente quello di utilizzare un transazioni distribuite con una risorsa JMS che partecipano a loro.

Tuttavia, come una soluzione rapida-and-dirty, si può tentare di creare un involucro attorno a un PlatformTransactionManager che eseguirà Runnable s registrati in alcune ThreadLocal stoccaggio dopo successo commettere (e rimuoverlo dal ThreadLocal dopo rollback). In realtà, AbstractPlatformTransactionManager ha esattamente questo tipo di logica con TransactionSynchronization s, ma è sepolto troppo in profondità nei dettagli di implementazione.

+0

Per ora sono andato con la soluzione rapida e sporca. Ho creato un meccanismo basato su ThreadLocal che associa una coda di oggetti Runnable a un oggetto TransactionStatus. In commit() o rollback() di un TransactionStatus I esegue gli oggetti Runnable o li elimina. Funziona abbastanza bene. – DrewEaster

+0

Puoi fornire uno snippet di codice? – Oliv

+0

Non direi che le transazioni distribuite sono la giusta soluzione _proper_. La transazione distribuita fallirebbe l'intera transazione se il solo invio della posta non fosse andato a buon fine. Ma il comportamento desiderato qui è che la posta dipende dalla transazione DB e la transazione DB non dipende dalla posta. – yair

4

È possibile utilizzare TransactionSynchronizationManager senza dover modificare PlatformTransactionManager.

Nota: TransactionAware è un'interfaccia marker che indica che ApplicationListener desidera ricevere un evento dopo che la transazione è stata eseguita correttamente.

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

questo funziona per me:

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

Bello! Migliore risposta in relazione alla domanda IMO. Questo è più simile a SwingUtilities.invokeLater. –

Problemi correlati