Problem
It's common practice to use a simple (no custom attributes) @Transactional
on methods to execute them wrapped in a transaction. Unfortunately, for methods declared as @TransactionalEventListener
, this doesn't work as expected:
@Component
class SomeListener {
@Transactional
@TransactionalEventListener
void on(SomeEvent event) { /* … */ }
}
Those methods are invoked in the transaction cleanup phase of an original, other transaction. This means that this particular, other transaction is still running, although in an undefined state. With a simple @Transactional
declared on those methods, the code would be executed in an undefined transactional state. An obvious solution to this problem is to declare a Propagation
of REQUIRES_NEW
on the listener method, so that it will run in a new transaction:
@Component
class SomeListener {
@Transactional(propagation = Propagation.REQUIRES_NEW)
@TransactionalEventListener
void on(SomeEvent event) { /* … */ }
}
Proposed solution
It would be nice if the erroneous arrangement shown above would be rejected, ideally at bootstrap (potentially even AOT?) time. We have to consider that a plain @Transactional
is still valid in the case of the method declared to be invoked asynchronously (using @Async
). In other words, a declaration like this is still valid, as the asynchronous invocation triggers the execution on a separate thread and thus a clean slate regarding transactions.
@Component
class SomeListener {
@Async
@Transactional
@TransactionalEventListener
void on(SomeEvent event) { /* … */ }
}
Comment From: quaff
After https://github.com/spring-projects/spring-framework/commit/842569c9e519ec7d11fc86691577cee067c7ef47
@Component
class SomeListener {
@Transactional(propagation = Propagation.REQUIRES_NEW)
@TransactionalEventListener
void on(SomeEvent event) { /* … */ }
}
will not be valid, is it intentional? @jhoeller
Comment From: jhoeller
Good point, the check seems too restrictive now. I'll revise it to be lenient towards REQUIRES_NEW.
Comment From: jhoeller
I'm inclined to enforce REQUIRES_NEW
here since that is semantically correct for such a listener in general. Even with @Async
execution, it would be self-descriptive to always declare REQUIRES_NEW
there. Also, our check in the transaction package cannot actually enforce actual async execution, it can only check for the presence of the common @Async
annotation without knowing whether the corresponding runtime setup is active.