-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Description
Bug description
When using ResourcelessTransactionManager, calling a method with @Transactional(propagation = Propagation.SUPPORTS) followed by a method with @Transactional(propagation = Propagation.REQUIRED_NEW) will result in a TransactionSuspensionNotSupportedException.
It seems ResourcelessTransactionManager creates an inactive ResourcelessTransaction during the first method call. The second method call then tries to suspend this "existing transaction".
Environment
Spring Framework 6.2.11
Spring Batch 5.2.3
Java 17
Expected behavior
We bumped into this issue while migrating a code base from Spring Batch 4 to Spring Batch 5.
ResourcelessTransactionManager not understanding Propagation.SUPPORTS presents a problem in Spring Batch 5, as many JobExplorer methods are now annotated with Propagation.SUPPORTS.
As a result, any call to a JobExplorer method (using SUPPORTS) followed by a call to a JobRepository method (using REQUIRES_NEW) consistently fails with a TransactionSuspensionNotSupportedException.
Minimal Complete Reproducible example
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SuspendTransactionExceptionReproducerTest.Config.class})
public class SuspendTransactionExceptionReproducerTest {
@Resource
private JobExplorer jobExplorer;
@Resource
private JobRepository jobRepository;
@Test
public void testSuspendTransactionNotSupportedException() {
// Step 1: JobExplorer.getJobNames() uses PROPAGATION_SUPPORTS
// Expected: Execute non-transactionally since no transaction exists
// Actual: ResourcelessTransactionManager creates a ResourcelessTransaction
jobExplorer.getJobNames();
// Step 2: JobRepository.getLastJobExecution() uses PROPAGATION_REQUIRES_NEW
// Expected: Start a new transaction (no existing transaction to suspend)
// Actual: Finds the bound ResourcelessTransaction, tries to suspend it
// Result: TransactionSuspensionNotSupportedException
jobRepository.getLastJobExecution("test", new JobParameters());
}
@Configuration
@EnableTransactionManagement
public static class Config {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("org/springframework/batch/core/schema-h2.sql")
.build();
}
@Bean
public PlatformTransactionManager transactionManager() {
return new ResourcelessTransactionManager();
}
@Bean
public JobRepositoryFactoryBean jobRepository() {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource());
factory.setTransactionManager(transactionManager());
return factory;
}
@Bean
public JobExplorerFactoryBean jobExplorer() {
JobExplorerFactoryBean factory = new JobExplorerFactoryBean();
factory.setDataSource(dataSource());
factory.setTransactionManager(transactionManager());
return factory;
}
}
}