Flow chart of Batch processing
Spring Batch constructed of 7 parts:
JobRepository: used for register Job container, set up database properties.
JobLauncher: used for start Job interface.
Job: the task that need to be excuted.
Step: just what it means.
ItemReader: read data from file and then map the field.
ItemProcessor: used for process data, meanwhile check it.
ItemWriter: used for output data, set up datasource, write pre-processing SQL command.
The project structure is this:
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.codenotfound.springbatchhelloworld; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class SpringBatchHelloWorldApplication { public static void main(String[] args) { SpringApplication.run(SpringBatchHelloWorldApplication.class, args); } }
3 Create model save the input data in the src/test/resources/csv/persons.csv
4 Map the data from csv to the Person object This is a simple POJO containing first name & last name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.codenotfound.model; public class Person { private String firstName; private String lastName; public Person () { // default constructor } public String getFirstName () { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName () { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } @Override public String toString (){ return firstName + " " + lastName; } }
5 Create BatchConfig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.codenotfound.batch; import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration @EnableBatchProcessing public class BatchConfig extends DefaultBatchConfigurer { @Override public void setDataSource(DataSource dataSource) { // initialize will use a Map based JobRepository (instead of database) } }
6 Create HelloWorldJobConfig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package com.codenotfound.batch; import com.codenotfound.model.Person; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; import org.springframework.batch.item.file.FlatFileItemReader; import org.springframework.batch.item.file.FlatFileItemWriter; import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder; import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder; import org.springframework.batch.item.file.transform.PassThroughLineAggregator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; @Configuration public class HelloWorldJobConfig { @Bean public Job helloWorldJob(JobBuilderFactory jobBuilders, StepBuilderFactory stepBuilders) { return jobBuilders.get("helloWorldJob" ) .start(helloWorldStep(stepBuilders)).build(); } @Bean public Step helloWorldStep(StepBuilderFactory stepBuilders) { return stepBuilders.get("helloWorldStep" ) .<Person, String>chunk(10).reader(reader()) .processor(processor()).writer(writer()).build(); } @Bean public FlatFileItemReader<Person> reader () { return new FlatFileItemReaderBuilder<Person>() .name("personItemReader" ) .resource(new ClassPathResource("csv/persons.csv" )) .delimited().names(new String[] {"firstName" , "lastName" }) .targetType(Person.class).build(); } @Bean public PersonItemProcessor processor () { return new PersonItemProcessor(); } @Bean public FlatFileItemWriter<String> writer () { return new FlatFileItemWriterBuilder<String>() .name("greetingItemWriter" ) .resource(new FileSystemResource( "target/test-outputs/greetings.txt" )) .lineAggregator(new PassThroughLineAggregator<>()).build(); } }
7 Create PersonItemProcessor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.codenotfound.batch; import com.codenotfound.model.Person; import org.springframework.batch.item.ItemProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PersonItemProcessor implements ItemProcessor<Person,String> { private static final Logger LOGGER = LoggerFactory.getLogger(PersonItemProcessor.class); @Override public String process(Person person) throws Exception { String greeting = "Hello " + person.getFirstName() + " " + person.getLastName() + "!" ; LOGGER.info("converting '{}' into '{}'" , person, greeting); return greeting; } }
8 Create SpringBatchHelloWorldApplicationTests 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package com.codenotfound.springbatchhelloworld; import static org.assertj.core.api.Assertions.assertThat; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.test.JobLauncherTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.test.context.junit4.SpringRunner; import com.codenotfound.batch.BatchConfig; import com.codenotfound.batch.HelloWorldJobConfig; @RunWith(SpringRunner.class) @SpringBootTest( classes = {SpringBatchHelloWorldApplicationTests.BatchTestConfig.class} ) public class SpringBatchHelloWorldApplicationTests { @Autowired private JobLauncherTestUtils jobLauncherTestUtils; @Test public void testHelloWorldJob() throws Exception { JobExecution jobExecution = jobLauncherTestUtils.launchJob(); assertThat(jobExecution.getExitStatus().getExitCode()) .isEqualTo("COMPLETED" ); } @Configuration @Import({BatchConfig.class, HelloWorldJobConfig.class}) static class BatchTestConfig { @Autowired private Job helloWorldJob; @Bean JobLauncherTestUtils jobLauncherTestUtils() throws NoSuchJobException { JobLauncherTestUtils jobLauncherTestUtils = new JobLauncherTestUtils(); jobLauncherTestUtils.setJob(helloWorldJob); return jobLauncherTestUtils; } } }
TEST
IF success, the terminal will show:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | ' _ | '_| | ' _ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.1.RELEASE) [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.514 s - in com.codenotfound.springbatchhelloworld.SpringBatchHelloWorldApplicationTests 2020-08-31 16:57:35.542 INFO 39420 --- [ Thread-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@10163d6: startup date [Mon Aug 31 16:57:34 CST 2020]; root of context hierarchy [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 9.110 s [INFO] Finished at: 2020-08-31T16:57:35+08:00 [INFO] ------------------------------------------------------------------------