/* * Copyright 2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.batch.core.test.concurrent; import static org.junit.Assert.assertEquals; import java.sql.Connection; import java.sql.Driver; import java.sql.SQLException; import java.sql.Statement; import javax.sql.DataSource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; import org.springframework.batch.core.job.builder.FlowBuilder; import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.jdbc.datasource.embedded.ConnectionProperties; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseConfigurer; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.ClassUtils; /** * @author Michael Minella */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = ConcurrentTransactionTests.ConcurrentJobConfiguration.class) public class ConcurrentTransactionTests { @Autowired private Job concurrentJob; @Autowired private JobLauncher jobLauncher; @DirtiesContext @Test public void testConcurrentLongRunningJobExecutions() throws Exception { JobExecution jobExecution = jobLauncher.run(concurrentJob, new JobParameters()); assertEquals(jobExecution.getStatus(), BatchStatus.COMPLETED); } @Configuration @EnableBatchProcessing public static class ConcurrentJobConfiguration extends DefaultBatchConfigurer { @Autowired private JobBuilderFactory jobBuilderFactory; @Autowired private StepBuilderFactory stepBuilderFactory; @Bean public TaskExecutor taskExecutor() { return new SimpleAsyncTaskExecutor(); } /** * This datasource configuration configures the HSQLDB instance using MVCC. When * configurd using the default behavior, transaction serialization errors are * thrown (default configuration example below). * * return new PooledEmbeddedDataSource(new EmbeddedDatabaseBuilder(). * addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql"). * addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql"). * build()); * @return */ @Bean DataSource dataSource() { ResourceLoader defaultResourceLoader = new DefaultResourceLoader(); EmbeddedDatabaseFactory embeddedDatabaseFactory = new EmbeddedDatabaseFactory(); embeddedDatabaseFactory.setDatabaseConfigurer(new EmbeddedDatabaseConfigurer() { @Override public void configureConnectionProperties(ConnectionProperties properties, String databaseName) { try { properties.setDriverClass((Class<? extends Driver>) ClassUtils.forName("org.hsqldb.jdbcDriver", this.getClass().getClassLoader())); } catch (Exception e) { e.printStackTrace(); } properties.setUrl("jdbc:hsqldb:mem:" + databaseName + ";hsqldb.tx=mvcc"); properties.setUsername("sa"); properties.setPassword(""); } @Override public void shutdown(DataSource dataSource, String databaseName) { try { Connection connection = dataSource.getConnection(); Statement stmt = connection.createStatement(); stmt.execute("SHUTDOWN"); } catch (SQLException ex) { } } }); ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(); databasePopulator.addScript(defaultResourceLoader.getResource("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql")); databasePopulator.addScript(defaultResourceLoader.getResource("classpath:org/springframework/batch/core/schema-hsqldb.sql")); embeddedDatabaseFactory.setDatabasePopulator(databasePopulator); return embeddedDatabaseFactory.getDatabase(); } @Bean public Flow flow() { return new FlowBuilder<Flow>("flow") .start(stepBuilderFactory.get("flow.step1") .tasklet(new Tasklet() { @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { return RepeatStatus.FINISHED; } }).build() ).next(stepBuilderFactory.get("flow.step2") .tasklet(new Tasklet() { @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { return RepeatStatus.FINISHED; } }).build() ).build(); } @Bean public Step firstStep() { return stepBuilderFactory.get("firstStep") .tasklet(new Tasklet() { @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { System.out.println(">> Beginning concurrent job test"); return RepeatStatus.FINISHED; } }).build(); } @Bean public Step lastStep() { return stepBuilderFactory.get("lastStep") .tasklet(new Tasklet() { @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { System.out.println(">> Ending concurrent job test"); return RepeatStatus.FINISHED; } }).build(); } @Bean public Job concurrentJob() { Flow splitFlow = new FlowBuilder<Flow>("splitflow").split(new SimpleAsyncTaskExecutor()).add(flow(), flow(), flow(), flow(), flow(), flow(), flow()).build(); return jobBuilderFactory.get("concurrentJob") .start(firstStep()) .next(stepBuilderFactory.get("splitFlowStep") .flow(splitFlow) .build()) .next(lastStep()) .build(); } @Override protected JobRepository createJobRepository() throws Exception { JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); factory.setDataSource(dataSource()); factory.setIsolationLevelForCreate("ISOLATION_READ_COMMITTED"); factory.setTransactionManager(getTransactionManager()); factory.afterPropertiesSet(); return factory.getObject(); } } }