/*
* Copyright 2016 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.item.database.builder;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import javax.sql.DataSource;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.database.JdbcCursorItemReader;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author Michael Minella
*/
public class JdbcCursorItemReaderBuilderTests {
private DataSource dataSource;
private ConfigurableApplicationContext context;
@Before
public void setUp() {
this.context = new AnnotationConfigApplicationContext(TestDataSourceConfiguration.class);
this.dataSource = (DataSource) context.getBean("dataSource");
}
@After
public void tearDown() {
if(this.context != null) {
this.context.close();
}
}
@Test
public void testSimpleScenario() throws Exception {
JdbcCursorItemReader<Foo> reader = new JdbcCursorItemReaderBuilder<Foo>()
.dataSource(this.dataSource)
.name("fooReader")
.sql("SELECT * FROM FOO ORDER BY FIRST")
.rowMapper((rs, rowNum) -> {
Foo foo = new Foo();
foo.setFirst(rs.getInt("FIRST"));
foo.setSecond(rs.getString("SECOND"));
foo.setThird(rs.getString("THIRD"));
return foo;
})
.build();
ExecutionContext executionContext = new ExecutionContext();
reader.open(executionContext);
validateFoo(reader.read(), 1, "2", "3");
validateFoo(reader.read(), 4, "5", "6");
validateFoo(reader.read(), 7, "8", "9");
assertNull(reader.read());
}
@Test
public void testMaxRows() throws Exception {
JdbcCursorItemReader<Foo> reader = new JdbcCursorItemReaderBuilder<Foo>()
.dataSource(this.dataSource)
.name("fooReader")
.sql("SELECT * FROM FOO ORDER BY FIRST")
.maxRows(2)
.saveState(false)
.rowMapper((rs, rowNum) -> {
Foo foo = new Foo();
foo.setFirst(rs.getInt("FIRST"));
foo.setSecond(rs.getString("SECOND"));
foo.setThird(rs.getString("THIRD"));
return foo;
})
.build();
ExecutionContext executionContext = new ExecutionContext();
reader.open(executionContext);
validateFoo(reader.read(), 1, "2", "3");
validateFoo(reader.read(), 4, "5", "6");
assertNull(reader.read());
reader.close();
assertEquals(0, executionContext.size());
}
@Test
public void testQueryArgumentsList() throws Exception {
JdbcCursorItemReader<Foo> reader = new JdbcCursorItemReaderBuilder<Foo>()
.dataSource(this.dataSource)
.name("fooReader")
.sql("SELECT * FROM FOO WHERE FIRST > ? ORDER BY FIRST")
.queryArguments(Arrays.asList(3))
.rowMapper((rs, rowNum) -> {
Foo foo = new Foo();
foo.setFirst(rs.getInt("FIRST"));
foo.setSecond(rs.getString("SECOND"));
foo.setThird(rs.getString("THIRD"));
return foo;
})
.build();
ExecutionContext executionContext = new ExecutionContext();
reader.open(executionContext);
validateFoo(reader.read(), 4, "5", "6");
validateFoo(reader.read(), 7, "8", "9");
assertNull(reader.read());
}
@Test
public void testQueryArgumentsArray() throws Exception {
JdbcCursorItemReader<Foo> reader = new JdbcCursorItemReaderBuilder<Foo>()
.dataSource(this.dataSource)
.name("fooReader")
.sql("SELECT * FROM FOO WHERE FIRST > ? ORDER BY FIRST")
.queryArguments(new Integer[] {3})
.rowMapper((rs, rowNum) -> {
Foo foo = new Foo();
foo.setFirst(rs.getInt("FIRST"));
foo.setSecond(rs.getString("SECOND"));
foo.setThird(rs.getString("THIRD"));
return foo;
})
.build();
ExecutionContext executionContext = new ExecutionContext();
reader.open(executionContext);
validateFoo(reader.read(), 4, "5", "6");
validateFoo(reader.read(), 7, "8", "9");
assertNull(reader.read());
}
@Test
public void testQueryArgumentsTypedArray() throws Exception {
JdbcCursorItemReader<Foo> reader = new JdbcCursorItemReaderBuilder<Foo>()
.dataSource(this.dataSource)
.name("fooReader")
.sql("SELECT * FROM FOO WHERE FIRST > ? ORDER BY FIRST")
.queryArguments(new Integer[] {3}, new int[] {Types.BIGINT})
.rowMapper((rs, rowNum) -> {
Foo foo = new Foo();
foo.setFirst(rs.getInt("FIRST"));
foo.setSecond(rs.getString("SECOND"));
foo.setThird(rs.getString("THIRD"));
return foo;
})
.build();
ExecutionContext executionContext = new ExecutionContext();
reader.open(executionContext);
validateFoo(reader.read(), 4, "5", "6");
validateFoo(reader.read(), 7, "8", "9");
assertNull(reader.read());
}
@Test
public void testPreparedStatementSetter() throws Exception {
JdbcCursorItemReader<Foo> reader = new JdbcCursorItemReaderBuilder<Foo>()
.dataSource(this.dataSource)
.name("fooReader")
.sql("SELECT * FROM FOO WHERE FIRST > ? ORDER BY FIRST")
.preparedStatementSetter(new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps) throws SQLException {
ps.setInt(1, 3);
}
})
.rowMapper((rs, rowNum) -> {
Foo foo = new Foo();
foo.setFirst(rs.getInt("FIRST"));
foo.setSecond(rs.getString("SECOND"));
foo.setThird(rs.getString("THIRD"));
return foo;
})
.build();
ExecutionContext executionContext = new ExecutionContext();
reader.open(executionContext);
validateFoo(reader.read(), 4, "5", "6");
validateFoo(reader.read(), 7, "8", "9");
assertNull(reader.read());
}
@Test
public void testMaxItemCount() throws Exception {
JdbcCursorItemReader<Foo> reader = new JdbcCursorItemReaderBuilder<Foo>()
.dataSource(this.dataSource)
.name("fooReader")
.sql("SELECT * FROM FOO ORDER BY FIRST")
.maxItemCount(2)
.rowMapper((rs, rowNum) -> {
Foo foo = new Foo();
foo.setFirst(rs.getInt("FIRST"));
foo.setSecond(rs.getString("SECOND"));
foo.setThird(rs.getString("THIRD"));
return foo;
})
.build();
ExecutionContext executionContext = new ExecutionContext();
reader.open(executionContext);
validateFoo(reader.read(), 1, "2", "3");
validateFoo(reader.read(), 4, "5", "6");
assertNull(reader.read());
}
@Test
public void testCurrentItemCount() throws Exception {
JdbcCursorItemReader<Foo> reader = new JdbcCursorItemReaderBuilder<Foo>()
.dataSource(this.dataSource)
.name("fooReader")
.sql("SELECT * FROM FOO ORDER BY FIRST")
.currentItemCount(1)
.rowMapper((rs, rowNum) -> {
Foo foo = new Foo();
foo.setFirst(rs.getInt("FIRST"));
foo.setSecond(rs.getString("SECOND"));
foo.setThird(rs.getString("THIRD"));
return foo;
})
.build();
ExecutionContext executionContext = new ExecutionContext();
reader.open(executionContext);
validateFoo(reader.read(), 4, "5", "6");
validateFoo(reader.read(), 7, "8", "9");
assertNull(reader.read());
}
@Test
public void testOtherProperties() {
JdbcCursorItemReader<Foo> reader = new JdbcCursorItemReaderBuilder<Foo>()
.dataSource(this.dataSource)
.name("fooReader")
.sql("SELECT * FROM FOO ORDER BY FIRST")
.fetchSize(1)
.queryTimeout(2)
.ignoreWarnings(true)
.driverSupportsAbsolute(true)
.useSharedExtendedConnection(true)
.rowMapper((rs, rowNum) -> {
Foo foo = new Foo();
foo.setFirst(rs.getInt("FIRST"));
foo.setSecond(rs.getString("SECOND"));
foo.setThird(rs.getString("THIRD"));
return foo;
})
.build();
assertEquals(1, ReflectionTestUtils.getField(reader, "fetchSize"));
assertEquals(2, ReflectionTestUtils.getField(reader, "queryTimeout"));
assertTrue((boolean) ReflectionTestUtils.getField(reader, "ignoreWarnings"));
assertTrue((boolean) ReflectionTestUtils.getField(reader, "driverSupportsAbsolute"));
}
@Test
public void testValidation() {
try {
new JdbcCursorItemReaderBuilder<Foo>().saveState(true).build();
}
catch (IllegalArgumentException iae) {
assertEquals("A name is required when saveSate is set to true", iae.getMessage());
}
catch (Exception e) {
fail();
}
try {
new JdbcCursorItemReaderBuilder<Foo>()
.saveState(false)
.build();
}
catch (IllegalArgumentException iae) {
assertEquals("A query is required", iae.getMessage());
}
catch (Exception e) {
fail();
}
try {
new JdbcCursorItemReaderBuilder<Foo>()
.saveState(false)
.sql("select 1")
.build();
}
catch (IllegalArgumentException iae) {
assertEquals("A datasource is required", iae.getMessage());
}
catch (Exception e) {
fail();
}
try {
new JdbcCursorItemReaderBuilder<Foo>()
.saveState(false)
.sql("select 1")
.dataSource(this.dataSource)
.build();
}
catch (IllegalArgumentException iae) {
assertEquals("A rowmapper is required", iae.getMessage());
}
catch (Exception e) {
fail();
}
}
private void validateFoo(Foo item, int first, String second, String third) {
assertEquals(first, item.getFirst());
assertEquals(second, item.getSecond());
assertEquals(third, item.getThird());
}
public static class Foo {
private int first;
private String second;
private String third;
public int getFirst() {
return first;
}
public void setFirst(int first) {
this.first = first;
}
public String getSecond() {
return second;
}
public void setSecond(String second) {
this.second = second;
}
public String getThird() {
return third;
}
public void setThird(String third) {
this.third = third;
}
}
@Configuration
public static class TestDataSourceConfiguration {
private static final String CREATE_SQL = "CREATE TABLE FOO (\n" +
"\tID BIGINT IDENTITY NOT NULL PRIMARY KEY ,\n" +
"\tFIRST BIGINT ,\n" +
"\tSECOND VARCHAR(5) NOT NULL,\n" +
"\tTHIRD VARCHAR(5) NOT NULL) ;";
private static final String INSERT_SQL =
"INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (1, '2', '3');" +
"INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (4, '5', '6');" +
"INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (7, '8', '9');";
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseFactory().getDatabase();
}
@Bean
public DataSourceInitializer initializer(DataSource dataSource) {
DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
dataSourceInitializer.setDataSource(dataSource);
Resource create = new ByteArrayResource(CREATE_SQL.getBytes());
Resource insert = new ByteArrayResource(INSERT_SQL.getBytes());
dataSourceInitializer.setDatabasePopulator(new ResourceDatabasePopulator(create, insert));
return dataSourceInitializer;
}
}
}