2010-08-16 8 views
11

Ich verwende Spring-Anwendungsereignisse innerhalb meiner Service-Schicht, um das breitere System zu benachrichtigen, wenn bestimmte Ereignisse auftreten. Das Problem, das ich habe, ist, dass die Ereignisse in einem transaktionalen Kontext ausgelöst werden (ich verwende Spring @Transactional Annotation). Die Gefahr besteht darin, dass ich ein Ereignis auslöst und dann die Transaktion beim Commit fehlschlägt (dies kann beispielsweise bei Verwendung von Hibernate passieren). In diesem Szenario gehen die Ereignis-Listener von einem Zustand aus, der nach der Ausführung zurückgesetzt wird. Die Gefahr besteht hier darin, dass ich beispielsweise eine E-Mail gesendet habe, um die Registrierung eines Nutzers auf einer Website zu bestätigen, obwohl sein Benutzerkonto tatsächlich nicht erstellt wurde.Spring Transaction Annotationen - Execute on Success

Gibt es eine Möglichkeit, unter Beibehaltung der Verwendung von Anmerkungen, irgendwo ein Ereignis zu markieren, das nach der Transaktion ausgelöst werden soll? Es scheint irgendwie analog zu SwingUtilities.invokeLater(Runnable runnable) bei GUI-Programmierung in Java Swing. Ich möchte sagen, führen Sie dieses Bit des Codes später aus, sobald die aktuelle Transaktion erfolgreich festgeschrieben wird.

Irgendwelche Ideen?

Danke,

Andrew

+0

Dies alles über Annotationen nativ im Frühjahr jetzt gehandhabt wird. Siehe https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2https://spring.io/blog/2015/02/11/besser -application-events-in-spring-framework-4-2 speziell für den Abschnitt über die neue Annotation @TransactionalEventListener. – PaulNUK

Antwort

2

Der richtige Weg, um Ihr Problem mit der Bestätigung E-Mail zu lösen, ist wahrscheinlich eine verteilte Transaktionen mit einer JMS-Ressource in ihnen die Teilnahme zu verwenden.

jedoch als eine schnelle und unsaubere Lösung, können Sie versuchen, einen Wrapper um einen PlatformTransactionManager zu schaffen, die Runnable s ausgeführt wird nach erfolgreicher begehen in einigen ThreadLocal Speicher registriert (und entfernen Sie sie aus ThreadLocal nach Rollback). Eigentlich AbstractPlatformTransactionManager haben genau diese Art von Logik mit TransactionSynchronization s, aber es ist zu tief in seine Implementierungsdetails vergraben.

+0

Ich bin jetzt mit der schnell-und-schmutzig-Lösung gegangen. Ich habe einen ThreadLocal-basierten Mechanismus erstellt, der eine Warteschlange von Runnable-Objekten einem TransactionStatus-Objekt zuordnet. Bei commit() oder rollback() eines TransactionStatus führe ich entweder die Runnable-Objekte aus oder verwerfe sie. Es funktioniert ganz gut. – DrewEaster

+0

Können Sie ein Code-Snippet bereitstellen? – Oliv

+0

Ich würde nicht sagen, dass verteilte Transaktionen die richtige _proper_ Lösung sind. Verteilte Transaktion würde die gesamte Transaktion fehlschlagen, wenn nur das Senden der Mail fehlgeschlagen ist. Aber das gewünschte Verhalten ist hier, dass die Mail von der DB-Transaktion abhängt und die DB-Transaktion nicht von der Mail abhängt. – yair

4

Sie können TransactionSynchronizationManager verwenden, ohne den PlatformTransactionManager zu hacken.

Hinweis: TransactionAware ist eine Markierungsschnittstelle, die angibt, dass ein ApplicationListener ein Ereignis empfangen möchte, nachdem die Transaktion erfolgreich übergeben wurde.

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

Dies funktioniert für mich:

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

Schön! Beste Antwort in Bezug auf die Frage IMO. Dies ist am ähnlichsten zu SwingUtilities.invokeLater. –

Verwandte Themen