2011-03-18 11 views
10

Tengo una aplicación de marco de resorte en la que me gustaría agregar un oyente de transacción a una transacción que está actualmente en progreso. La motivación es activar una acción posterior a la confirmación que notifique a los sistemas posteriores. Estoy usando @Transactional para ajustar una transacción en torno a algún método de servicio, que es donde quiero crear/registrar el oyente de transacción posterior. Quiero hacer algo como "me gusta" lo siguiente.registra dinámicamente el detector de transacciones con spring?

public class MyService { 
@Transaction 
public void doIt() { 
    modifyObjects(); 

    // something like this 
    getTransactionManager().registerPostCommitAction(new 
    TransactionSynchronizationAdapter() { 
    public void afterCommit() { 
     notifyDownstream(); 
    } 
    }); 
} 
} 

Spring tiene una interfaz TransactionSynchronization y clase de adaptador que parece exactamente lo que quiero; sin embargo, no está claro cómo registrar uno dinámicamente con la transacción actual o con el administrador de transacciones. Prefiero no subclasificar JtaTransactionManager si puedo evitarlo.

Q: Alguien ha hecho esto antes.

Q: ¿Cuál es la forma más sencilla de registrar mi adaptador?

Respuesta

3

podría utilizar un aspecto aspect to match métodos transaccionales en su servicio para lograr esto:

@Aspect 
public class AfterReturningExample { 

    @AfterReturning("execution(* com.mypackage.MyService.*(..))") 
    public void afterReturning() { 
    // ... 
    } 

} 
+0

Una buena sugerencia, sin embargo, ¿esto no se ejecutará en ninguna transacción? Solo quiero una finalización completa. Además, ¿puedo usar esas anotaciones AOP con resorte predeterminado (no estoy usando código tejiendo) solo proxies JDK. – Justin

+0

El aviso de devolución no se ejecuta cuando los métodos arrojan una excepción, por lo que esto se basa en la confirmación @Transactional cuando el método devuelve el resultado y retrocede al lanzar la excepción, que generalmente es lo que sucede. Los aspectos se llaman a través de proxies dinámicos de resortes, por lo que no hacen ningún tipo de código (a menos que no se proporcionen interfaces para el proxy al principio, donde la primavera volverá a tejer el código en tiempo de ejecución con cglib). – krock

+1

+1 por interesante, pero aún no estoy convencido sin probar. Hay al menos 2 @AfterReturning involucrados: @Transactional y luego mi aspecto personalizado. Si @Transactional se ejecuta, entonces la mía se ejecuta, todo está bien; sin embargo, en el otro orden, ejecutaré antes, independientemente del estado de confirmación de transacción. – Justin

26

En realidad no fue tan duro como pensaba; spring tiene una clase de ayudante estática que coloca las cosas 'correctas' en el contexto del hilo.

TransactionSynchronizationManager.registerSynchronization(
    new TransactionSynchronizationAdapter() { 
     @Override 
     public void afterCommit() { 
      s_logger.info("TRANSACTION COMPLETE!!!"); 
     } 
    } 
); 
+0

+1 para una respuesta agradable y directa. No sabía que existiera TransactionSynchronizationManager. – krock

+0

En realidad, esta solución no es tan buena como lo había pensado. El contrato para TransactionSynchronizationManager es más extenso, y el intento de enviar un mensaje JMS dentro de afterCommit() falla (sin excepción) ya que el código anterior cumple con el contrato. – Justin

+0

La mayoría de las soluciones de mensajería de Spring (JMS, AMQP) le permiten agregar un plugin en su administrador de transacciones para que las transacciones entre los dos sistemas (db, message broker) se sincronicen. No quería hacer esto porque RabbitMQ TX es muy lento. En consecuencia, es posible que desee ver mi solución. –

2

Aquí es una solución más completa que hice para un problema similar que con el deseo de mis mensajes enviados después de las transacciones se confirman (Podría haber usado RabbitMQ TX, sino que son más bien lento).

public class MessageBusUtils { 
    public static Optional<MessageBusResourceHolder> getTransactionalResourceHolder(TxMessageBus messageBus) { 

     if (! TransactionSynchronizationManager.isActualTransactionActive()) { 
      return Optional.absent(); 
     } 

     MessageBusResourceHolder o = (MessageBusResourceHolder) TransactionSynchronizationManager.getResource(messageBus); 
     if (o != null) return Optional.of(o); 

     o = new MessageBusResourceHolder(); 
     TransactionSynchronizationManager.bindResource(messageBus, o); 
     o.setSynchronizedWithTransaction(true); 
     if (TransactionSynchronizationManager.isSynchronizationActive()) { 
      TransactionSynchronizationManager.registerSynchronization(new MessageBusResourceSynchronization(o, messageBus)); 
     } 
     return Optional.of(o); 

    } 

    private static class MessageBusResourceSynchronization extends ResourceHolderSynchronization<MessageBusResourceHolder, TxMessageBus> { 
     private final TxMessageBus messageBus; 
     private final MessageBusResourceHolder holder; 

     public MessageBusResourceSynchronization(MessageBusResourceHolder resourceHolder, TxMessageBus resourceKey) { 
      super(resourceHolder, resourceKey); 
      this.messageBus = resourceKey; 
      this.holder = resourceHolder; 
     } 


     @Override 
     protected void cleanupResource(MessageBusResourceHolder resourceHolder, TxMessageBus resourceKey, 
       boolean committed) { 
      resourceHolder.getPendingMessages().clear(); 
     } 

     @Override 
     public void afterCompletion(int status) { 
      if (status == TransactionSynchronization.STATUS_COMMITTED) { 
       for (Object o : holder.getPendingMessages()) { 
        messageBus.post(o, false); 
       } 
      } 
      else { 
       holder.getPendingMessages().clear(); 
      } 
      super.afterCompletion(status); 
     } 


    } 
} 

public class MessageBusResourceHolder extends ResourceHolderSupport { 

    private List<Object> pendingMessages = Lists.newArrayList(); 

    public void addMessage(Object message) { 
     pendingMessages.add(message); 
    } 


    protected List<Object> getPendingMessages() { 
     return pendingMessages; 
    } 

} 

En su clase en la que realmente envía el mensaje que va a hacer

@Override 
public void postAfterCommit(Object o) { 
    Optional<MessageBusResourceHolder> holder = MessageBusTxUtils.getTransactionalResourceHolder(this); 
    if (holder.isPresent()) { 
     holder.get().addMessage(o); 
    } 
    else { 
     post(o, false); 
    } 
} 

Lo siento por las largas muestras de codificación interminables pero es de esperar que mostrar a alguien cómo hacer algo después de una confirmación.

+0

Estoy usando su código para actualizar el índice de 3 solr después de la transacción comprometida. También estoy utilizando el evento Spring para detectar la modificación de Business Objects, almacenar eventos en ResourceHolder y actualizar el índice que debe actualizarse de forma asincrónica. –

0

Tiene sentido anular el administrador de transacciones en los métodos de compromiso y retrotracción, llamando al super.commit() desde el principio.

Cuestiones relacionadas