diff --git a/pom.xml b/pom.xml index 3fca452552..17e4fe1584 100644 --- a/pom.xml +++ b/pom.xml @@ -617,7 +617,8 @@ spring-aop spring-apache-camel - spring-batch + spring-batch + spring-batch-2 spring-bom spring-boot-modules spring-boot-rest diff --git a/spring-batch-2/pom.xml b/spring-batch-2/pom.xml new file mode 100644 index 0000000000..54df6d43e8 --- /dev/null +++ b/spring-batch-2/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + spring-batch-2 + 0.1-SNAPSHOT + spring-batch-2 + jar + http://maven.apache.org + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../parent-boot-2 + + + + + org.springframework.boot + spring-boot-starter-batch + 2.3.6.RELEASE + + + org.hsqldb + hsqldb + ${hsqldb.version} + runtime + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.batch.version} + test + + + org.junit.vintage + junit-vintage-engine + + + + + org.springframework.batch + spring-batch-test + ${spring.batch.test.version} + test + + + + + 2.3.6.RELEASE + 4.2.4.RELEASE + 2.5.1 + + + diff --git a/spring-batch-2/src/main/java/com/baeldung/batch/BatchConfiguration.java b/spring-batch-2/src/main/java/com/baeldung/batch/BatchConfiguration.java new file mode 100644 index 0000000000..0c053dd86c --- /dev/null +++ b/spring-batch-2/src/main/java/com/baeldung/batch/BatchConfiguration.java @@ -0,0 +1,81 @@ +package com.baeldung.batch; + +import javax.sql.DataSource; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +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.launch.support.RunIdIncrementer; +import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider; +import org.springframework.batch.item.database.JdbcBatchItemWriter; +import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; +import org.springframework.batch.item.file.FlatFileItemReader; +import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder; +import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; + +@Configuration +@EnableBatchProcessing +public class BatchConfiguration { + + @Autowired + public JobBuilderFactory jobBuilderFactory; + + @Autowired + public StepBuilderFactory stepBuilderFactory; + + @Value("${file.input}") + private String fileInput; + + @Bean + public FlatFileItemReader reader() { + return new FlatFileItemReaderBuilder().name("coffeeItemReader") + .resource(new ClassPathResource(fileInput)) + .delimited() + .names(new String[] { "brand", "origin", "characteristics" }) + .fieldSetMapper(new BeanWrapperFieldSetMapper() {{ + setTargetType(Coffee.class); + }}) + .build(); + } + + @Bean + public CoffeeItemProcessor processor() { + return new CoffeeItemProcessor(); + } + + @Bean + public JdbcBatchItemWriter writer(DataSource dataSource) { + return new JdbcBatchItemWriterBuilder().itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) + .sql("INSERT INTO coffee (brand, origin, characteristics) VALUES (:brand, :origin, :characteristics)") + .dataSource(dataSource) + .build(); + } + + @Bean + public Job importUserJob(JobCompletionNotificationListener listener, Step step1) { + return jobBuilderFactory.get("importUserJob") + .incrementer(new RunIdIncrementer()) + .listener(listener) + .flow(step1) + .end() + .build(); + } + + @Bean + public Step step1(JdbcBatchItemWriter writer) { + return stepBuilderFactory.get("step1") + . chunk(10) + .reader(reader()) + .processor(processor()) + .writer(writer) + .build(); + } + +} diff --git a/spring-batch-2/src/main/java/com/baeldung/batch/Coffee.java b/spring-batch-2/src/main/java/com/baeldung/batch/Coffee.java new file mode 100644 index 0000000000..4dfcd9959c --- /dev/null +++ b/spring-batch-2/src/main/java/com/baeldung/batch/Coffee.java @@ -0,0 +1,47 @@ +package com.baeldung.batch; + +public class Coffee { + + private String brand; + private String origin; + private String characteristics; + + public Coffee() { + } + + public Coffee(String brand, String origin, String characteristics) { + this.brand = brand; + this.origin = origin; + this.characteristics = characteristics; + } + + public String getBrand() { + return brand; + } + + public void setBrand(String brand) { + this.brand = brand; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public String getCharacteristics() { + return characteristics; + } + + public void setCharacteristics(String characteristics) { + this.characteristics = characteristics; + } + + @Override + public String toString() { + return "Coffee [brand=" + getBrand() + ", origin=" + getOrigin() + ", characteristics=" + getCharacteristics() + "]"; + } + +} diff --git a/spring-batch-2/src/main/java/com/baeldung/batch/CoffeeItemProcessor.java b/spring-batch-2/src/main/java/com/baeldung/batch/CoffeeItemProcessor.java new file mode 100644 index 0000000000..b154b80453 --- /dev/null +++ b/spring-batch-2/src/main/java/com/baeldung/batch/CoffeeItemProcessor.java @@ -0,0 +1,24 @@ +package com.baeldung.batch; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.batch.item.ItemProcessor; + +public class CoffeeItemProcessor implements ItemProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(CoffeeItemProcessor.class); + + @Override + public Coffee process(final Coffee coffee) throws Exception { + String brand = coffee.getBrand().toUpperCase(); + String origin = coffee.getOrigin().toUpperCase(); + String chracteristics = coffee.getCharacteristics().toUpperCase(); + + Coffee transformedCoffee = new Coffee(brand, origin, chracteristics); + LOGGER.info("Converting ( {} ) into ( {} )", coffee, transformedCoffee); + + return transformedCoffee; + } + +} diff --git a/spring-batch-2/src/main/java/com/baeldung/batch/JobCompletionNotificationListener.java b/spring-batch-2/src/main/java/com/baeldung/batch/JobCompletionNotificationListener.java new file mode 100644 index 0000000000..ca1de40aea --- /dev/null +++ b/spring-batch-2/src/main/java/com/baeldung/batch/JobCompletionNotificationListener.java @@ -0,0 +1,34 @@ +package com.baeldung.batch; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.listener.JobExecutionListenerSupport; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +@Component +public class JobCompletionNotificationListener extends JobExecutionListenerSupport { + + private static final Logger LOGGER = LoggerFactory.getLogger(JobCompletionNotificationListener.class); + + private final JdbcTemplate jdbcTemplate; + + @Autowired + public JobCompletionNotificationListener(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public void afterJob(JobExecution jobExecution) { + if (jobExecution.getStatus() == BatchStatus.COMPLETED) { + LOGGER.info("!!! JOB FINISHED! Time to verify the results"); + + String query = "SELECT brand, origin, characteristics FROM coffee"; + jdbcTemplate.query(query, (rs, row) -> new Coffee(rs.getString(1), rs.getString(2), rs.getString(3))) + .forEach(coffee -> LOGGER.info("Found < {} > in the database.", coffee)); + } + } +} diff --git a/spring-batch-2/src/main/java/com/baeldung/batch/SpringBootBatchProcessingApplication.java b/spring-batch-2/src/main/java/com/baeldung/batch/SpringBootBatchProcessingApplication.java new file mode 100644 index 0000000000..7682124b8d --- /dev/null +++ b/spring-batch-2/src/main/java/com/baeldung/batch/SpringBootBatchProcessingApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.batch; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootBatchProcessingApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootBatchProcessingApplication.class, args); + } + +} diff --git a/spring-batch-2/src/main/resources/application.properties b/spring-batch-2/src/main/resources/application.properties new file mode 100644 index 0000000000..0b8c56d3f8 --- /dev/null +++ b/spring-batch-2/src/main/resources/application.properties @@ -0,0 +1 @@ +file.input=coffee-list.csv \ No newline at end of file diff --git a/spring-batch-2/src/main/resources/coffee-list.csv b/spring-batch-2/src/main/resources/coffee-list.csv new file mode 100644 index 0000000000..6ceef00556 --- /dev/null +++ b/spring-batch-2/src/main/resources/coffee-list.csv @@ -0,0 +1,3 @@ +Blue Mountain,Jamaica,Fruity +Lavazza,Colombia,Strong +Folgers,America,Smokey \ No newline at end of file diff --git a/spring-batch-2/src/main/resources/logback.xml b/spring-batch-2/src/main/resources/logback.xml new file mode 100644 index 0000000000..7d900d8ea8 --- /dev/null +++ b/spring-batch-2/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/spring-batch-2/src/main/resources/schema-all.sql b/spring-batch-2/src/main/resources/schema-all.sql new file mode 100644 index 0000000000..9f698f7d81 --- /dev/null +++ b/spring-batch-2/src/main/resources/schema-all.sql @@ -0,0 +1,8 @@ +DROP TABLE coffee IF EXISTS; + +CREATE TABLE coffee ( + coffee_id BIGINT IDENTITY NOT NULL PRIMARY KEY, + brand VARCHAR(20), + origin VARCHAR(20), + characteristics VARCHAR(30) +); \ No newline at end of file diff --git a/spring-batch-2/src/test/java/com/baeldung/batch/SpringBootBatchIntegrationTest.java b/spring-batch-2/src/test/java/com/baeldung/batch/SpringBootBatchIntegrationTest.java new file mode 100644 index 0000000000..ba2b8a6a13 --- /dev/null +++ b/spring-batch-2/src/test/java/com/baeldung/batch/SpringBootBatchIntegrationTest.java @@ -0,0 +1,49 @@ +package com.baeldung.batch; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobInstance; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobRepositoryTestUtils; +import org.springframework.batch.test.context.SpringBatchTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.PropertySource; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +@SpringBatchTest +@SpringBootTest +@DirtiesContext +@PropertySource("classpath:application.properties") +@RunWith(SpringRunner.class) +public class SpringBootBatchIntegrationTest { + + @Autowired + private JobLauncherTestUtils jobLauncherTestUtils; + + @Autowired + private JobRepositoryTestUtils jobRepositoryTestUtils; + + @After + public void cleanUp() { + jobRepositoryTestUtils.removeJobExecutions(); + } + + @Test + public void givenCoffeeList_whenJobExecuted_thenSuccess() throws Exception { + JobExecution jobExecution = jobLauncherTestUtils.launchJob(); + JobInstance jobInstance = jobExecution.getJobInstance(); + ExitStatus jobExitStatus = jobExecution.getExitStatus(); + + assertThat(jobInstance.getJobName(), is("importUserJob")); + assertThat(jobExitStatus.getExitCode(), is("COMPLETED")); + } + +}