Java or Spring async TaskExeuctor that supports transaction association?

Viewed 638

I'm trying to work with an @TransactionalEventListener (Spring 4.3) that will run an event after a transaction completes.

However, I've got my own ApplicationEventMulticaster that I'm building and using a ThreadPoolTaskExecutor for.

The docs for SimpleApplicationEventMulticaster state:

    /**
     * Set a custom executor (typically a {@link org.springframework.core.task.TaskExecutor})
     * to invoke each listener with.
     * <p>Default is equivalent to {@link org.springframework.core.task.SyncTaskExecutor},
     * executing all listeners synchronously in the calling thread.
     * <p>Consider specifying an asynchronous task executor here to not block the
     * caller until all listeners have been executed. However, note that asynchronous
     * execution will not participate in the caller's thread context (class loader,
     * transaction association) unless the TaskExecutor explicitly supports this.
     * @see org.springframework.core.task.SyncTaskExecutor
     * @see org.springframework.core.task.SimpleAsyncTaskExecutor
     */
    public void setTaskExecutor(Executor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

However, I am unable to locate an Async TaskExecutor that is transaction aware. Does something like this exist in the Java ecosystem already?

Currently, my events never get ran because the ThreadPoolTaskExecutor is not transaction aware, so it always thinks there is no current transaction - ignoring the Event Listener.

1 Answers

No, there is currently on stable TaskExecutor which will work with spring managed transactions as currently, Spring binds transactions to the Threads via ThreadLocals.

But you can easily have asynchronous @TransactionalEventListener in your code by just adding @Async annotation on your @TransactionalEventListener listener methods. Just remember that the Spring will publish the event to the listener only when the publisher was called in the boundaries of the transaction and its after commit phase, but the transaction finishes so the object is in the detached state.

So say if you need to publish event from within a Transaction

@Service
public class Service {

        @Autowired
        private SomeRepository someRepository;

        @Autowired
        private ApplicationEventPublisher applicationEventPublisher;

        @Transactional
        public void create(SomeEntity entity) {
            someRepository.save(entity);
            SomeEntityCreatedEvent event = new SomeEntityCreatedEvent();
            event.entity = entity;
            applicationEventPublisher.publishEvent(event);
        }

        public static final class SomeEntityCreatedEvent {
            private SomeEntity entity;

            public SomeEntity getSomeEntity() {
               return entity;
            }
        }
}

The listener will get the event with already detached entity, so changes on it won't be propagated to the datastore.

@Component
public class UpdatesListener {

    @Async // here you can controll the task executor which needs to take care of this thread
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleCustom(Service.SomeEntityCreatedEvent event) {
        // here entity is deatached
        SomeEntity entity = event.getSomeEntity();
        System.out.println("Created some entity with id" + entity.getId());
    }

}

Ahh, and do not overwrite the default SyncTaskExecutor on the SimpleApplicationEventMulticaster instead use @Async on listeners.

Related