2010-08-16 4 views
11

특정 이벤트가 발생할 때 더 넓은 시스템에 알리기 위해 서비스 레이어 내에서 Spring 어플리케이션 이벤트를 사용하고 있습니다. 내가 가진 문제는 이벤트가 트랜잭션 컨텍스트 내에서 발생한다는 것입니다 (Spring의 @Transactional 주석을 사용하고 있습니다). 위험은 내가 이벤트를 시작한 다음 트랜잭션이 커밋에 실패하는 것입니다 (예를 들어 최대 절전 모드를 사용할 때 발생할 수 있음). 이 시나리오에서 이벤트 리스너는 실행 된 후에 롤백 될 상태를 가정하게됩니다. 예를 들어 실제로 사용자 계정이 실제로 만들어지지 않았을 때 웹 사이트에서 사용자의 등록을 확인하기 위해 전자 메일을 보냈을 수도 있습니다.Spring 트랜잭션 어노테이션 - 성공시 실행

어노테이션의 사용을 유지하면서 트랜잭션이 커밋 된 후 해고 될 이벤트에 플래그를 지정하는 방법이 있습니까? Java Swing에서 GUI 프로그래밍을 할 때 SwingUtilities.invokeLater(Runnable runnable)을 사용하는 것과 다소 비슷한 것처럼 보입니다. 현재 트랜잭션이 성공적으로 커밋되면 나중에이 코드를 실행한다고 말하고 싶습니다.

아이디어가 있으십니까?

감사합니다,

앤드류

+0

이 모든 주석을 통해 지금 봄에 기본적으로 처리됩니다. 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에서 특히 새로운 @TransactionalEventListener 주석에 대한 섹션을 참조하십시오. – PaulNUK

답변

2

확인 이메일로 문제를 해결하는 적절한 방법은 그들에 참가하는 JMS 리소스와 분산 트랜잭션을 사용하는 아마.

그러나, 빠른 - 및 - 더러운 솔루션으로, 당신은 커밋 성공적인 후 일부 ThreadLocal 저장에 등록 Runnable의를 실행하는 PlatformTransactionManager 래퍼를 만들려고 할 수 있습니다 (롤백 후 ThreadLocal에서 제거). 사실, AbstractPlatformTransactionManager은 정확히 TransactionSynchronization과 같은 종류의 로직을 가지고 있지만 구현 세부 사항에 너무 깊이 파묻혀 있습니다.

+0

저는 지금 당장 빠르고 간단하게 해결했습니다. Runnable 객체의 큐를 TransactionStatus 객체와 연관시키는 ThreadLocal 기반 메커니즘을 구축했습니다. TransactionStatus의 commit() 또는 rollback()에서 Runnable 객체를 실행하거나 폐기합니다. 그것은 아주 잘 작동합니다. – DrewEaster

+0

코드 스 니펫을 제공해 주시겠습니까? – Oliv

+0

분산 트랜잭션이 올바른 _proper_ 솔루션이라는 것은 아닙니다. 단순히 메일을 보내는 데 실패한 경우 분산 트랜잭션은 전체 트랜잭션에 실패합니다. 그러나 여기서 바람직한 동작은 메일이 DB 트랜잭션에 의존하고 DB 트랜잭션이 메일에 의존하지 않는다는 것입니다. – yair

4

PlatformTransactionManager를 해킹하지 않고도 TransactionSynchronizationManager를 사용할 수 있습니다.

주 : TransactionAware는 트랜잭션이 성공적으로 커밋 된 후에 ApplicationListener가 이벤트를 수신하려고한다는 것을 나타내는 마커 인터페이스입니다.

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

이 나를 위해 작동합니다

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

니스! IMO 질문과 관련하여 가장 좋은 답변입니다. 이것은 SwingUtilities.invokeLater와 가장 유사합니다. –

관련 문제