Issue
We upgraded Spring Boot from 1.5.8 to 2.6.2. It has introduced a problem that has us perplexed: Transactional saves are not processing from inside spawned threads.
We use JPA managed entities on a Mysql database and make calls down to the CrudRepository to save them.
Transactions inside the main thread work fine. However, when called from an asynchronous operation things go awry:
- both async and sync calls go through the Spring
SimpleJpaRepository.save()
method. But the entityManager returns the object to persist with a null id in the case of the async operation. - I followed the flow through in both types of calls and can see that the save propagates down to the
org.hibernate.internal.SessionImpl
service. - From there it makes its way to AbstractSaveEventListener.class and that is where the discrepancy appears to be. In the performSaveOrReplicate method (hibernate-core:5.6.3),
inTrx
boolean is false in the async workflow whereas it is true in the synch one. Because of that theshouldDelayIdentityInserts
flag gets set and an id does not appear to be generated for any entities in this thread.
We have tried different things to get this to work. For example, we used the transactionTemplate to have some specific control here, but that has not changed the behavior.
We were originally creating this async process by using the ApplicationEventPublisher to create an event. We also tried using completablefuture and other constructs with the same result as well as annotating the method with @Async and calling it directly.
Solution
The issue was that, with the upgrade to Spring Boot 2.6, Spring Batch implements a new Transaction Manager.
What we didn't realize is that this transaction manager was being autowired into our other services and did not work in this threaded context. You do not want to share a Batch processing Tx Manager with your API/misc services. Declaring a specific Transaction Manager there to keep them separate solved the issue.
Answered By - IcedDante
Answer Checked By - Marilyn (JavaFixing Volunteer)