Using Spring Boot 2.4.5 / Spring Framework 5.3.6.
I have a @TransactionalEventListener
and a more old-fashioned TransactionSynchronization
. I want to execute both after transaction commits, and the order matters.
Here is a sample test.
@SpringBootTest
@AutoConfigureTestDatabase
@EnableTransactionManagement
@ImportAutoConfiguration(DataSourceTransactionManagerAutoConfiguration.class)
class PostTxTest {
@Configuration
static class Config {
@Bean
SampleRepository sampleRepository(ApplicationEventPublisher eventPublisher) {
return new SampleRepository(eventPublisher);
}
@TransactionalEventListener
@Order(Ordered.HIGHEST_PRECEDENCE)
void earlyListener(SampleEvent event) {
System.out.println("early listener called !!!");
}
}
static class SampleRepository {
private final ApplicationEventPublisher eventPublisher;
public SampleRepository(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@Transactional
public void execute() {
eventPublisher.publishEvent(new SampleEvent());
TransactionSynchronizationManager.registerSynchronization(new LateSynchronization());
}
}
static class SampleEvent extends ApplicationEvent {
public SampleEvent() {
super("");
}
}
static class LateSynchronization implements TransactionSynchronization {
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public void afterCommit() {
System.out.println("late synchronization called !!!");
}
}
@Test
void test(@Autowired SampleRepository repository) {
repository.execute();
}
}
The output is
late synchronization called !!!
early listener called !!!
When debugging, it looks like AbstractPlatformTransactionManager
does
try {
// here the TransactionSynchronization.afterCommit() is called
triggerAfterCommit(status);
}
finally {
// and here the TransactionalApplicationListenerSynchronization.afterCompletion() is called
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
TransactionalApplicationListenerSynchronization
will call the event listener method. Though, it is too late for me.
- Is this a bug or is this by design?
- If this is by design (which is sad to me), I guess this behavior should be documented somewhere. I haven't found such docs.
Comment From: snicoll
I am not sure what is the actual problem here. It looks like you'd like an event listener to be called before a manually registered TransactionSynchronization
but there's no such guarantee anywhere. The ordering is relative to even listeners, and the callback is invoked after the commit as documented in the javadoc. If I've missed something, please provide more details and we can reconsider.
Comment From: mdleonid
hi @snicoll
Thanks for looking into this.
I think the same phase name ('after commit') for both mechanisms is what causes the confusion.
* for event listeners the @TransactionalEventListener
has
TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
* and for manual synchronization the afterCommit()
method is used
So, my impression was that they are both 'after commit', both apply at the same phase, and ordering should apply to both. However, due to the different underlying mechanisms, this is not really the same phase.
As you said, there is no explicit guarantee that these after commits are the same :) My point is that it may be worth documenting to prevent possible confusion.
Comment From: snicoll
Thanks for following-up!
So, my impression was that they are both 'after commit'.
The Javadoc of TransactionPhase#AFTER_COMMIT
states:
Handle the event after the commit has completed successfully. Note: This is a specialization of AFTER_COMPLETION and therefore executes in the same sequence of events as AFTER_COMPLETION (and not in TransactionSynchronization.afterCommit()).
The javadoc on ordering also states:
Adding @Order to your annotated method allows you to prioritize that listener amongst other listeners running before or after transaction completion.
I am going to close this now as I believe that things are documented properly.