diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/resource/JobParametersPreparedStatementSetter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/resource/JobParametersPreparedStatementSetter.java new file mode 100644 index 000000000..704460f2b --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/resource/JobParametersPreparedStatementSetter.java @@ -0,0 +1,76 @@ +/* + * Copyright 2006-2008 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.resource; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.listener.StepListenerSupport; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.jdbc.core.PreparedStatementSetter; +import org.springframework.jdbc.core.SqlTypeValue; +import org.springframework.jdbc.core.StatementCreatorUtils; +import org.springframework.util.Assert; + +/** + * Implementation of the {@link PreparedStatementSetter} interface that also implements + * {@link StepListener} and uses {@link JobParameters} to set the parameters on a + * PreparedStatement. + * + * @author Lucas Ward + * + */ +public class JobParametersPreparedStatementSetter extends StepListenerSupport implements + PreparedStatementSetter, InitializingBean { + + private List parameterKeys; + private JobParameters jobParameters; + + public void setValues(PreparedStatement ps) throws SQLException { + Map parameters = jobParameters.getParameters(); + for(int i = 0; i < parameterKeys.size(); i++){ + Object arg = parameters.get(parameterKeys.get(i)); + if(arg == null){ + throw new IllegalStateException("No job parameter found for with key of: [" + parameterKeys.get(i) + "]"); + } + StatementCreatorUtils.setParameterValue(ps, i + 1, SqlTypeValue.TYPE_UNKNOWN, arg); + } + } + + public void beforeStep(StepExecution stepExecution) { + this.jobParameters = stepExecution.getJobParameters(); + } + + /** + * The parameter names that will be pulled from the {@link JobParameters}. It is + * assumed that their order in the List is the order of the parameters in the + * PreparedStatement. + * + * @return + */ + public void setParameterKeys(List parameterKeys) { + this.parameterKeys = parameterKeys; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(parameterKeys, "Parameters names must be provided"); + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/resource/Foo.java b/spring-batch-core/src/test/java/org/springframework/batch/core/resource/Foo.java new file mode 100644 index 000000000..2b07f9c5f --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/resource/Foo.java @@ -0,0 +1,54 @@ +package org.springframework.batch.core.resource; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +/** + * Simple domain object for testing purposes. + */ +public class Foo { + + private int id; + private String name; + private int value; + + public Foo(){} + + public Foo(int id, String name, int value) { + this.id = id; + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public int getValue() { + return value; + } + public void setValue(int value) { + this.value = value; + } + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + public String toString() { + return "Foo[id=" +id +",name=" + name + ",value=" + value + "]"; + } + + public boolean equals(Object obj) { + return EqualsBuilder.reflectionEquals(this, obj); + } + + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/resource/FooRowMapper.java b/spring-batch-core/src/test/java/org/springframework/batch/core/resource/FooRowMapper.java new file mode 100644 index 000000000..117e376cf --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/resource/FooRowMapper.java @@ -0,0 +1,20 @@ +package org.springframework.batch.core.resource; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.springframework.jdbc.core.RowMapper; + + +public class FooRowMapper implements RowMapper { + + public Object mapRow(ResultSet rs, int rowNum) throws SQLException { + + Foo foo = new Foo(); + foo.setId(rs.getInt(1)); + foo.setName(rs.getString(2)); + foo.setValue(rs.getInt(3)); + + return foo; + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/resource/JdbcCursorItemReaderPreparedStatementIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/resource/JdbcCursorItemReaderPreparedStatementIntegrationTests.java new file mode 100644 index 000000000..0e5fff50e --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/resource/JdbcCursorItemReaderPreparedStatementIntegrationTests.java @@ -0,0 +1,62 @@ +package org.springframework.batch.core.resource; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.SimpleJob; +import org.springframework.batch.core.step.tasklet.TaskletStep; +import org.springframework.batch.item.database.JdbcCursorItemReader; +import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests; + +public class JdbcCursorItemReaderPreparedStatementIntegrationTests extends + AbstractTransactionalDataSourceSpringContextTests { + + JdbcCursorItemReader itemReader; + + protected void onSetUpInTransaction() throws Exception { + super.onSetUpInTransaction(); + + itemReader = new JdbcCursorItemReader(); + itemReader.setDataSource(super.getJdbcTemplate().getDataSource()); + itemReader.setSql("select ID, NAME, VALUE from T_FOOS where ID > ? and ID < ?"); + itemReader.setIgnoreWarnings(true); + itemReader.setVerifyCursorPosition(true); + + itemReader.setMapper(new FooRowMapper()); + itemReader.setFetchSize(10); + itemReader.setMaxRows(100); + itemReader.setQueryTimeout(1000); + itemReader.setSaveState(true); + JobParametersPreparedStatementSetter pss = new JobParametersPreparedStatementSetter(); + JobParameters jobParameters = new JobParametersBuilder().addLong("begin.id", new Long(1)).addLong("end.id", new Long(4)).toJobParameters(); + JobInstance jobInstance = new JobInstance(new Long(1), jobParameters, new SimpleJob()); + JobExecution jobExecution = new JobExecution(jobInstance, new Long(2)); + StepExecution stepExecution = new StepExecution(new TaskletStep(), jobExecution, new Long(3) ); + pss.beforeStep(stepExecution); + + List parameterNames = new ArrayList(); + parameterNames.add("begin.id"); + parameterNames.add("end.id"); + pss.setParameterKeys(parameterNames); + + itemReader.setPreparedStatementSetter(pss); + } + + public void testRead() throws Exception{ + + Foo foo = (Foo)itemReader.read(); + assertEquals(2, foo.getId()); + foo = (Foo)itemReader.read(); + assertEquals(3, foo.getId()); + assertNull(itemReader.read()); + } + + protected String[] getConfigLocations() { + return new String[] { "org/springframework/batch/core/resource/data-source-context.xml" }; + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/resource/JobParametersPreparedStatementSetterTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/resource/JobParametersPreparedStatementSetterTests.java new file mode 100644 index 000000000..3ada02bd4 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/resource/JobParametersPreparedStatementSetterTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2006-2008 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.resource; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.SimpleJob; +import org.springframework.batch.core.step.tasklet.TaskletStep; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowCallbackHandler; +import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests; +import org.springframework.util.ClassUtils; + +/** + * @author Lucas Ward + * + */ +public class JobParametersPreparedStatementSetterTests extends AbstractTransactionalDataSourceSpringContextTests { + + JdbcTemplate jdbcTemplate; + JobParametersPreparedStatementSetter pss; + + protected String[] getConfigLocations() { + return new String[] { ClassUtils.addResourcePathToPackagePath(getClass(), "data-source-context.xml") }; + } + + protected void onSetUpInTransaction() throws Exception { + super.onSetUpInTransaction(); + + pss = new JobParametersPreparedStatementSetter(); + JobParameters jobParameters = new JobParametersBuilder().addLong("begin.id", new Long(1)).addLong("end.id", new Long(4)).toJobParameters(); + JobInstance jobInstance = new JobInstance(new Long(1), jobParameters, new SimpleJob()); + JobExecution jobExecution = new JobExecution(jobInstance, new Long(2)); + StepExecution stepExecution = new StepExecution(new TaskletStep(), jobExecution, new Long(3) ); + pss.beforeStep(stepExecution); + jdbcTemplate = getJdbcTemplate(); + } + + public void testSetValues(){ + + List parameterNames = new ArrayList(); + parameterNames.add("begin.id"); + parameterNames.add("end.id"); + pss.setParameterKeys(parameterNames); + final List results = new ArrayList(); + jdbcTemplate.query("SELECT NAME from T_FOOS where ID > ? and ID < ?", pss, new RowCallbackHandler(){ + + public void processRow(ResultSet rs) throws SQLException { + results.add(rs.getString(1)); + }}); + + assertEquals(2, results.size()); + assertEquals("bar2", results.get(0)); + assertEquals("bar3", results.get(1)); + } + + public void testAfterPropertiesSet() throws Exception{ + try{ + pss.afterPropertiesSet(); + fail(); + } + catch(IllegalArgumentException ex){ + //expected + } + } + + public void testNonExistentProperties(){ + + List parameterNames = new ArrayList(); + parameterNames.add("badParameter"); + parameterNames.add("end.id"); + pss.setParameterKeys(parameterNames); + + try{ + jdbcTemplate.query("SELECT NAME from T_FOOS where ID > ? and ID < ?", pss, new RowCallbackHandler(){ + + public void processRow(ResultSet rs) throws SQLException { + fail(); + }}); + + fail(); + }catch(IllegalStateException ex){ + //expected + } + + } +} diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/resource/data-source-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/resource/data-source-context.xml new file mode 100644 index 000000000..5c65efecd --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/resource/data-source-context.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/resource/destroy-foo-schema-hsqldb.sql b/spring-batch-core/src/test/resources/org/springframework/batch/core/resource/destroy-foo-schema-hsqldb.sql new file mode 100644 index 000000000..1c03cad82 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/resource/destroy-foo-schema-hsqldb.sql @@ -0,0 +1,2 @@ +DROP TABLE T_FOOS; +DROP TABLE T_WRITE_FOOS; \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/resource/init-foo-schema-hsqldb.sql b/spring-batch-core/src/test/resources/org/springframework/batch/core/resource/init-foo-schema-hsqldb.sql new file mode 100644 index 000000000..84490ed2f --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/resource/init-foo-schema-hsqldb.sql @@ -0,0 +1,21 @@ +CREATE TABLE T_FOOS ( + ID BIGINT NOT NULL, + NAME VARCHAR(45), + VALUE INTEGER +); + +ALTER TABLE T_FOOS ADD PRIMARY KEY (ID); + +INSERT INTO t_foos (id, name, value) VALUES (1, 'bar1', 1); +INSERT INTO t_foos (id, name, value) VALUES (2, 'bar2', 2); +INSERT INTO t_foos (id, name, value) VALUES (3, 'bar3', 3); +INSERT INTO t_foos (id, name, value) VALUES (4, 'bar4', 4); +INSERT INTO t_foos (id, name, value) VALUES (5, 'bar5', 5); + +CREATE TABLE T_WRITE_FOOS ( + ID BIGINT NOT NULL, + NAME VARCHAR(45), + VALUE INTEGER +); + +ALTER TABLE T_WRITE_FOOS ADD PRIMARY KEY (ID);