@Service
@RequiredArgsContructor
public class MainService {
private final ApplicationEventPublisher publisher;
@Transactional
public void mainProcess() {
doSomething();
publisher.publishEvent(MainEvent.create());
}
}
@Component
@RequiredArgsContructor
public class MainEventHandleService {
private final MainRepository repository;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional
public void postPurchase(MainEvent event) {
doAfterMainProcessCommit();
}
private void doAfterMainProcessCommit() {
repository.save(MainEntity.builder().build()); // jpa save
}
}
MainService의 mainProcess가 commit을 완료하면, event를 통해 MainEntity를 저장하는 후속작업을 진행하려고 했다. 하지만 원하는대로 MainEntity가 저장이 되지 않은 문제가 발생하였다.
그 이유를 찾고자 TransactionalEventListener의 docs를 살펴보았다.
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {
/**
* Phase to bind the handling of an event to.
* <p>The default phase is {@link TransactionPhase#AFTER_COMMIT}.
* <p>If no transaction is in progress, the event is not processed at
* all unless {@link #fallbackExecution} has been enabled explicitly.
*/
TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
// 중략
}
TransactionalEventListener 의 기본 TransactionPhase는 AFTER_COMMIT이다.
/**
* The phase in which a transactional event listener applies.
*
* @author Stephane Nicoll
* @author Juergen Hoeller
* @author Sam Brannen
* @since 4.2
* @see TransactionalEventListener#phase()
* @see TransactionalApplicationListener#getTransactionPhase()
* @see TransactionalApplicationListener#forPayload(TransactionPhase, Consumer)
*/
public enum TransactionPhase {
/**
* Handle the event before transaction commit.
* @see TransactionSynchronization#beforeCommit(boolean)
*/
BEFORE_COMMIT,
/**
* Handle the event after the commit has completed successfully.
* <p>Note: This is a specialization of {@link #AFTER_COMPLETION} and therefore
* executes in the same sequence of events as {@code AFTER_COMPLETION}
* (and not in {@link TransactionSynchronization#afterCommit()}).
* <p>Interactions with the underlying transactional resource will not be
* committed in this phase. See
* {@link TransactionSynchronization#afterCompletion(int)} for details.
* @see TransactionSynchronization#afterCompletion(int)
* @see TransactionSynchronization#STATUS_COMMITTED
*/
AFTER_COMMIT,
// 중략
}
TransactionPhase는 TransactionalEventListener가 적용되는 단계를 의미한다. 이때 TransactionPhase.AFTER_COMMIT은 commit이 성공적으로 완료된 이후 event를 처리하도록 설정하는 단계이다.
mainProcess가 성공적으로 commit이 완료되었고, doAfterMainProcessCommit() 메소드가 실행되는 걸 디버깅을 통해 확인할 수 있었지만 실제 insert query가 나가진 않았다.
AFTER_COMMIT doc에서 link로 달아둔 TransactionSynchronization.afterCommit()
에 대한 설명을 더 살펴보자.
/**
* Interface for transaction synchronization callbacks.
* Supported by AbstractPlatformTransactionManager.
*
* <p>TransactionSynchronization implementations can implement the Ordered interface
* to influence their execution order. A synchronization that does not implement the
* Ordered interface is appended to the end of the synchronization chain.
*
* <p>System synchronizations performed by Spring itself use specific order values,
* allowing for fine-grained interaction with their execution order (if necessary).
*
* <p>Implements the {@link Ordered} interface to enable the execution order of
* synchronizations to be controlled declaratively, as of 5.3. The default
* {@link #getOrder() order} is {@link Ordered#LOWEST_PRECEDENCE}, indicating
* late execution; return a lower value for earlier execution.
*
* @author Juergen Hoeller
* @since 02.06.2003
* @see TransactionSynchronizationManager
* @see AbstractPlatformTransactionManager
* @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
*/
public interface TransactionSynchronization extends Ordered, Flushable {
// 중략
/**
* Invoked after transaction commit. Can perform further operations right
* <i>after</i> the main transaction has <i>successfully</i> committed.
* <p>Can e.g. commit further operations that are supposed to follow on a successful
* commit of the main transaction, like confirmation messages or emails.
* <p><b>NOTE:</b> The transaction will have been committed already, but the
* transactional resources might still be active and accessible. As a consequence,
* any data access code triggered at this point will still "participate" in the
* original transaction, allowing to perform some cleanup (with no commit following
* anymore!), unless it explicitly declares that it needs to run in a separate
* transaction. Hence: <b>Use {@code PROPAGATION_REQUIRES_NEW} for any
* transactional operation that is called from here.</b>
* @throws RuntimeException in case of errors; will be <b>propagated to the caller</b>
* (note: do not throw TransactionException subclasses here!)
*/
default void afterCommit() {
}
// 중략
}
NOTE 부분을 보자. afterCommit에서 호출되는 모든 트랜잭션 작업에는 PROPAGATION_REQUIRES_NEW 을 사용하라고 명시되어있다.
즉, TransactionalEventListener에서 commit이 완료되었고 이후의 추가적인 변경 작업에 대해서는 commit이 발생하지 않기 때문에 (
with no commit following anymore!
) afterCommit에서 호출되는 모든 트랜잭션 작업에는 PROPAGATION_REQUIRES_NEW 을 사용해야 한다.
PROPAGATION_REQUIRES_NEW은 @Transactional의 Propagation 옵션으로
/**
* Enumeration that represents transaction propagation behaviors for use
* with the {@link Transactional} annotation, corresponding to the
* {@link TransactionDefinition} interface.
*
* @author Colin Sampaleanu
* @author Juergen Hoeller
* @since 1.2
*/
public enum Propagation {
// 중략
/**
* Create a new transaction, and suspend the current transaction if one exists.
* Analogous to the EJB transaction attribute of the same name.
* <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
* on all transaction managers. This in particular applies to
* {@link org.springframework.transaction.jta.JtaTransactionManager},
* which requires the {@code javax.transaction.TransactionManager} to be
* made available to it (which is server-specific in standard Java EE).
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
// 중략
Propagation은 Transactional 어노테이션과 함께 사용되는 "트랜잭션 전파 동작"에 관한 enum 클래스이다. 이때 Propagation.REQUIRES_NEW은 새 트랜잭션을 생성하고 현재 트랜잭션이 있다면 이를 일시중지한다.
@Component
@RequiredArgsContructor
public class MainEventHandleService {
private final MainRepository repository;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional(propagation = Propagation.REQUIRES_NEW) // 👈👈
public void postPurchase(PostPurchaseEvent event) {
doAfterMainProcessCommit();
}
private void doAfterMainProcessCommit() {
repository.save(MainEntity.builder().build()); // jpa save
}
}