12
votes

Annotations de transaction de printemps - exécuter sur le succès

J'utilise des événements d'application à ressort dans ma couche de service pour notifier au système plus large lorsque des événements spécifiques se produisent. Le problème que j'ai est que les événements sont tirés dans un contexte transactionnel (j'utilise @Transactional annotation). Le danger est que je déclenche un événement, puis la transaction échoue sur commit (peut se produire lors de l'utilisation, par exemple, hibernate). Dans ce scénario, les auditeurs d'événements supposeront un état qui sera ensuite retouché après avoir exécuté. Le danger ici est que je peux, par exemple, avoir envoyé un courrier électronique pour confirmer l'enregistrement d'un utilisateur sur un site Web lorsque, en fait, leur compte d'utilisateur n'a pas été créé.

Y a-t-il un moyen, en préservant l'utilisation d'annotations, de signaler un événement à tirer un événement après la marche de la transaction? Il semble quelque peu analogue à utiliser oscillabilité.invokelater (runnable annulable) lors de la programmation de l'interface graphique à Java Swing. Je tiens à dire, exécutez ce bit de code plus tard une fois que la transaction actuelle s'engage avec succès.

Des idées?

merci,

Andrew


1 commentaires

Tout cela est manipulé de manière native au printemps maintenant via des annotations. Voir Spring.IO/11/2015/02/11/... Spécifiquement la section sur le nouveau @TransAderalEventListenner annotation.


3 Réponses :


2
votes

La bonne façon de résoudre votre problème avec les e-mails de confirmation est probablement d'utiliser une transaction distribuée avec une ressource JMS y participant.

Cependant, comme une solution rapide et sale, vous pouvez essayer de créer une enveloppe autour d'un PlateformeTransActionManager qui exécutera Runnable S enregistré dans certains ThreadLocal STOCKAGE Après avoir réussi à commenter (et retirez-le de ThreadLocal après la restauration). En fait, abstractplatformtransactionManager ont exactement ce type de logique avec transactionsNchronisation S, mais il est enterré trop profondément dans ses détails de mise en œuvre.


2 commentaires

Je suis allé avec la solution rapide et sale pour l'instant. J'ai construit un mécanisme à base de threadlocal qui associe une file d'attente d'objets runnables avec un objet transacstatus. Sur COMMIT () ou Rollback () d'une transactionStatus, j'exécute les objets runnables ou je les rejeter. Ça marche assez bien.


Je ne dirais pas que les transactions distribuées sont la solution appropriée . La transaction distribuée échouerait toute la transaction si seulement l'envoi du courrier a échoué. Mais le comportement souhaité est que le courrier dépend de la transaction DB et de la transaction DB ne dépend pas du courrier.



4
votes

Vous pouvez utiliser transcomptionYnchronizationManager sans avoir besoin de pirater la plate-formeTransactionManager.

Remarque: TransactionAware est une interface de marqueur indiquant qu'un applicationListener souhaite recevoir un événement après la transaction commis avec succès. P>

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


0 commentaires

12
votes

Cela fonctionne pour moi:

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


2 commentaires

Agréable ! Meilleure réponse en relation avec la question imo. Ceci est le plus similaire aux swinguties.Invokelater.


@Oliv Le problème vient lorsque l'unité teste ce morceau de code.