From 5f12a83609ee9220c843380d353d3e1482d22061 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 25 Sep 2023 10:30:19 +0200 Subject: [PATCH] Add java configuration for the Jdbc cursor sample Issue #3663 --- spring-batch-samples/README.md | 12 +-- .../JdbcCursorReaderBatchWriterSampleJob.java | 93 +++++++++++++++++++ .../batch/sample/jdbc/README.md | 30 ++++++ .../resources/jobs/iosample/jdbcCursor.xml | 24 ----- .../batch/sample/jdbc/job/jdbcCursor.xml | 38 ++++++++ .../batch/sample/jdbc/sql/schema.sql | 19 ++++ .../iosample/JdbcCursorFunctionalTests.java | 37 -------- .../jdbc/JdbcCursorFunctionalTests.java | 70 ++++++++++++++ 8 files changed, 252 insertions(+), 71 deletions(-) create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/sample/jdbc/JdbcCursorReaderBatchWriterSampleJob.java create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/sample/jdbc/README.md delete mode 100644 spring-batch-samples/src/main/resources/jobs/iosample/jdbcCursor.xml create mode 100644 spring-batch-samples/src/main/resources/org/springframework/batch/sample/jdbc/job/jdbcCursor.xml create mode 100644 spring-batch-samples/src/main/resources/org/springframework/batch/sample/jdbc/sql/schema.sql delete mode 100644 spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/JdbcCursorFunctionalTests.java create mode 100644 spring-batch-samples/src/test/java/org/springframework/batch/sample/jdbc/JdbcCursorFunctionalTests.java diff --git a/spring-batch-samples/README.md b/spring-batch-samples/README.md index 58abb260de..e3d883cad4 100644 --- a/spring-batch-samples/README.md +++ b/spring-batch-samples/README.md @@ -101,21 +101,13 @@ remote client (such as JConsole from the JDK) which does not have Spring Batch on the classpath. See the Spring Core Reference Guide for more details on how to customise the JMX configuration. -### Jdbc Cursor and Batch Update +### Jdbc Cursor and Batch Update sample The purpose of this sample is to show to usage of the `JdbcCursorItemReader` and the `JdbcBatchItemWriter` to make efficient updates to a database table. -The `JdbcBatchItemWriter` accepts a special form of -`PreparedStatementSetter` as a (mandatory) dependency. This is -responsible for copying fields from the item to be written to a -`PreparedStatement` matching the SQL query that has been -injected. The implementation of the -`CustomerCreditUpdatePreparedStatementSetter` shows best -practice of keeping all the information needed for the execution in -one place, since it contains a static constant value (`QUERY`) -which is used to configure the query for the writer. +[Jdbc Cursor and Batch Update sample](./src/main/java/org/springframework/batch/sample/jdbc/README.md) ### Amqp Job Sample diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/jdbc/JdbcCursorReaderBatchWriterSampleJob.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jdbc/JdbcCursorReaderBatchWriterSampleJob.java new file mode 100644 index 0000000000..65e4bf4b9a --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jdbc/JdbcCursorReaderBatchWriterSampleJob.java @@ -0,0 +1,93 @@ +/* + * Copyright 2023 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 + * + * https://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.sample.jdbc; + +import javax.sql.DataSource; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider; +import org.springframework.batch.item.database.JdbcBatchItemWriter; +import org.springframework.batch.item.database.JdbcCursorItemReader; +import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; +import org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder; +import org.springframework.batch.sample.domain.trade.CustomerCredit; +import org.springframework.batch.sample.domain.trade.internal.CustomerCreditIncreaseProcessor; +import org.springframework.batch.sample.domain.trade.internal.CustomerCreditRowMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.jdbc.support.JdbcTransactionManager; + +/** + * @author Mahmoud Ben Hassine + */ +@Configuration +@EnableBatchProcessing +public class JdbcCursorReaderBatchWriterSampleJob { + + @Bean + public JdbcCursorItemReader itemReader() { + String sql = "select ID, NAME, CREDIT from CUSTOMER"; + return new JdbcCursorItemReaderBuilder().name("customerReader") + .dataSource(dataSource()) + .sql(sql) + .rowMapper(new CustomerCreditRowMapper()) + .build(); + } + + @Bean + public JdbcBatchItemWriter itemWriter() { + String sql = "UPDATE CUSTOMER set credit = :credit where id = :id"; + return new JdbcBatchItemWriterBuilder().dataSource(dataSource()) + .sql(sql) + .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) + .assertUpdates(true) + .build(); + } + + @Bean + public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager) { + return new JobBuilder("ioSampleJob", jobRepository) + .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .reader(itemReader()) + .processor(new CustomerCreditIncreaseProcessor()) + .writer(itemWriter()) + .build()) + .build(); + } + + // Infrastructure beans + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL) + .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") + .addScript("/org/springframework/batch/core/schema-hsqldb.sql") + .addScript("/org/springframework/batch/sample/jdbc/sql/schema.sql") + .build(); + } + + @Bean + public JdbcTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } + +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/jdbc/README.md b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jdbc/README.md new file mode 100644 index 0000000000..4bede67bff --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jdbc/README.md @@ -0,0 +1,30 @@ +### Jdbc Cursor and Batch Update sample + +## About + +The purpose of this sample is to show to usage of the +`JdbcCursorItemReader` and the `JdbcBatchItemWriter` to make +efficient updates to a database table. + +The `JdbcBatchItemWriter` accepts a special form of +`PreparedStatementSetter` as a (mandatory) dependency. This is +responsible for copying fields from the item to be written to a +`PreparedStatement` matching the SQL query that has been +injected. The implementation of the +`CustomerCreditUpdatePreparedStatementSetter` shows best +practice of keeping all the information needed for the execution in +one place, since it contains a static constant value (`QUERY`) +which is used to configure the query for the writer. + +## Run the sample + +You can run the sample from the command line as following: + +``` +$>cd spring-batch-samples +# Launch the sample using the XML configuration +$>../mvnw -Dtest=JdbcCursorFunctionalTests#testLaunchJobWithXmlConfiguration test +# Launch the sample using the Java configuration +$>../mvnw -Dtest=JdbcCursorFunctionalTests#testLaunchJobWithJavaConfiguration test +``` + diff --git a/spring-batch-samples/src/main/resources/jobs/iosample/jdbcCursor.xml b/spring-batch-samples/src/main/resources/jobs/iosample/jdbcCursor.xml deleted file mode 100644 index c9286db477..0000000000 --- a/spring-batch-samples/src/main/resources/jobs/iosample/jdbcCursor.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/sample/jdbc/job/jdbcCursor.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/sample/jdbc/job/jdbcCursor.xml new file mode 100644 index 0000000000..ac44a19088 --- /dev/null +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/sample/jdbc/job/jdbcCursor.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/sample/jdbc/sql/schema.sql b/spring-batch-samples/src/main/resources/org/springframework/batch/sample/jdbc/sql/schema.sql new file mode 100644 index 0000000000..727f186169 --- /dev/null +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/sample/jdbc/sql/schema.sql @@ -0,0 +1,19 @@ +DROP TABLE CUSTOMER_SEQ IF EXISTS; +DROP TABLE CUSTOMER IF EXISTS; + +CREATE TABLE CUSTOMER_SEQ ( + ID BIGINT IDENTITY +); +INSERT INTO CUSTOMER_SEQ (ID) values (5); + +CREATE TABLE CUSTOMER ( + ID BIGINT IDENTITY NOT NULL PRIMARY KEY , + VERSION BIGINT , + NAME VARCHAR(45) , + CREDIT DECIMAL(10,2) +) ; + +INSERT INTO CUSTOMER (ID, VERSION, NAME, CREDIT) VALUES (1, 0, 'customer1', 100000); +INSERT INTO CUSTOMER (ID, VERSION, NAME, CREDIT) VALUES (2, 0, 'customer2', 100000); +INSERT INTO CUSTOMER (ID, VERSION, NAME, CREDIT) VALUES (3, 0, 'customer3', 100000); +INSERT INTO CUSTOMER (ID, VERSION, NAME, CREDIT) VALUES (4, 0, 'customer4', 100000); \ No newline at end of file diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/JdbcCursorFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/JdbcCursorFunctionalTests.java deleted file mode 100644 index ab70c370b2..0000000000 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/JdbcCursorFunctionalTests.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2006-2022 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 - * - * https://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.sample.iosample; - -import org.springframework.batch.item.ItemReader; -import org.springframework.batch.sample.domain.trade.CustomerCredit; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -/** - * @author Dan Garrette - * @author Glenn Renfro - * @author Mahmoud Ben Hassine - * @since 2.0 - */ -@SpringJUnitConfig(locations = "/jobs/iosample/jdbcCursor.xml") -class JdbcCursorFunctionalTests extends AbstractIoSampleTests { - - @Override - protected void pointReaderToOutput(ItemReader reader) { - // no-op - } - -} diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/jdbc/JdbcCursorFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/jdbc/JdbcCursorFunctionalTests.java new file mode 100644 index 0000000000..a8f94033ee --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/jdbc/JdbcCursorFunctionalTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2006-2023 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 + * + * https://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.sample.jdbc; + +import org.junit.jupiter.api.Test; + +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.launch.JobLauncher; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Dan Garrette + * @author Glenn Renfro + * @author Mahmoud Ben Hassine + * @since 2.0 + */ +@SpringJUnitConfig(locations = { "/org/springframework/batch/sample/jdbc/job/jdbcCursor.xml", + "/simple-job-launcher-context.xml", "/job-runner-context.xml" }) +class JdbcCursorFunctionalTests { + + @Autowired + private JobLauncherTestUtils jobLauncherTestUtils; + + @Test + void testLaunchJobWithXmlConfig() throws Exception { + // when + JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(); + + // then + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + + @Test + public void testLaunchJobWithJavaConfig() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(JdbcCursorReaderBatchWriterSampleJob.class); + JobLauncher jobLauncher = context.getBean(JobLauncher.class); + Job job = context.getBean(Job.class); + + // when + JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + + // then + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + +}