When creating a partitioned job in spring-batch that uses a JdbcCursorItemReader
and a Hikari connection pool with maximumPoolSize=1,
the partitioned step always throws CannotCreateTransactionException: Could not open JDBC Connection for transaction
at the very beginning of the step execution. This also occurs when gridSize
is set to any value greater than the maximumPoolSize
.
This somewhat implies that parallelism is limited to the maximum number of connections your pool can provide. What am I doing wrong here?
In the example below, I have maximumPoolSize=1
and gridSize=2
and the job always fails with:
org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction
at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:313) ~[spring-jdbc-6.1.6.jar:6.1.6]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager.java:531) ~[spring-tx-6.1.6.jar:6.1.6]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:405) ~[spring-tx-6.1.6.jar:6.1.6]
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:617) ~[spring-tx-6.1.6.jar:6.1.6]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:386) ~[spring-tx-6.1.6.jar:6.1.6]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.1.6.jar:6.1.6]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.6.jar:6.1.6]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) ~[spring-aop-6.1.6.jar:6.1.6]
at jdk.proxy2/jdk.proxy2.$Proxy49.updateExecutionContext(Unknown Source) ~[na:na]
at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:234) ~[spring-batch-core-5.1.1.jar:5.1.1]
at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:229) ~[spring-batch-core-5.1.1.jar:5.1.1]
at org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler.lambda$createTask$0(TaskExecutorPartitionHandler.java:132) ~[spring-batch-core-5.1.1.jar:5.1.1]
Setting maximumPoolSize=3
and gridSize=2
allows the job to complete succesfully.
Here is my job configuration:
@Configuration
@SpringBootApplication
public class AppConfiguration {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(AppConfiguration.class, args);
SpringApplication.exit(ctx);
}
@Autowired
PlatformTransactionManager transactionManager;
@Autowired
JobRepository jobRepo;
@Autowired
DataSource dataSource;
@Bean
public Job frrMockJob() {
return new JobBuilder("jobName", jobRepo).start(partitionedStep()).build();
}
@Bean
Step partitionedStep() {
return new StepBuilder("partitionedStep", jobRepo).partitioner(workStep())
.partitioner("workerStep", new SimplePartitioner())
.gridSize(2)
.taskExecutor(taskExecutor())
.build();
}
@Bean
public Step workStep() {
return new StepBuilder("step1", jobRepo)
.<String, String>chunk(2, transactionManager)
.reader(reader())
.writer(writer())
.build();
}
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor t = new ThreadPoolTaskExecutor();
t.setCorePoolSize(10);
t.setMaxPoolSize(10);
return t;
}
@Bean
@StepScope
JdbcCursorItemReader<String> reader() {
JdbcCursorItemReader<String> reader = new JdbcCursorItemReader<>();
reader.setDataSource(dataSource);
reader.setSql("SELECT 1");
reader.setRowMapper((rs, i) -> rs.getString(1));
reader.setPreparedStatementSetter(ps -> {
});
return reader;
}
@Bean
@StepScope
ItemWriter<String> writer() {
return new ItemWriter<String>() {
@Override
public void write(Chunk<? extends String> chunk) throws Exception {
System.out.println(chunk.toString());
}
};
}
}
Here is my DataSource:
@Configuration
public class AppDataSourceConfiguration {
@Bean
public DataSource datasource() {
HikariDataSource hikariDataSource = (HikariDataSource) DataSourceBuilder
.create(HikariDataSource.class.getClassLoader())
.username("sa").password("")
.url("jdbc:h2:mem:atest;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false")
.driverClassName("org.h2.Driver")
.build();
hikariDataSource.setMaximumPoolSize(1);
hikariDataSource.setConnectionTimeout(5000);
return hikariDataSource;
}
}
My pom is very simple and uses spring-boot-starter-batch
version 3.2.5
LloydB is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.