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:
SimpleJpaRepository.save()
method. But the entityManager returns the object to persist with a null id in the case of the async operation.org.hibernate.internal.SessionImpl
service.inTrx
boolean is false in the async workflow whereas it is true in the synch one. Because of that the shouldDelayIdentityInserts
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.
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.
Here is an example marking a PlatformTransactionManager with the Primary
annotation to test its usage explicitly.
@Primary
@Bean
public PlatformTransactionManager platformTransactionManager(DataSource dataSource) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory(dataSource).getObject());
return transactionManager;
}