/* * 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.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.sql.DataSource; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.batch.item.database.JdbcBatchItemWriter; 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.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 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.assertTrue; import static org.junit.Assert.fail; /** * @author Michael Minella */ public class JdbcBatchItemWriterBuilderTests { 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 testBasicMap() throws Exception { JdbcBatchItemWriter<Map<String, Object>> writer = new JdbcBatchItemWriterBuilder<Map<String, Object>>() .columnMapped() .dataSource(this.dataSource) .sql("INSERT INTO FOO (first, second, third) VALUES (:first, :second, :third)") .build(); writer.afterPropertiesSet(); List<Map<String, Object>> items = buildMapItems(); writer.write(items); verifyWrite(); } @Test public void testCustomJdbcTemplate() throws Exception { NamedParameterJdbcOperations template = new NamedParameterJdbcTemplate(this.dataSource); JdbcBatchItemWriter<Map<String, Object>> writer = new JdbcBatchItemWriterBuilder<Map<String, Object>>() .columnMapped() .namedParametersJdbcTemplate(template) .sql("INSERT INTO FOO (first, second, third) VALUES (:first, :second, :third)") .build(); writer.afterPropertiesSet(); List<Map<String, Object>> items = buildMapItems(); writer.write(items); verifyWrite(); Object usedTemplate = ReflectionTestUtils.getField(writer, "namedParameterJdbcTemplate"); assertTrue(template == usedTemplate); } @Test public void testBasicPojo() throws Exception { JdbcBatchItemWriter<Foo> writer = new JdbcBatchItemWriterBuilder<Foo>() .beanMapped() .dataSource(this.dataSource) .sql("INSERT INTO FOO (first, second, third) VALUES (:first, :second, :third)") .build(); writer.afterPropertiesSet(); List<Foo> items = new ArrayList<>(3); items.add(new Foo(1, "two", "three")); items.add(new Foo(4, "five", "six")); items.add(new Foo(7, "eight", "nine")); writer.write(items); verifyWrite(); } @Test(expected = EmptyResultDataAccessException.class) public void testAssertUpdates() throws Exception { JdbcBatchItemWriter<Foo> writer = new JdbcBatchItemWriterBuilder<Foo>() .beanMapped() .dataSource(this.dataSource) .sql("UPDATE FOO SET second = :second, third = :third WHERE first = :first") .assertUpdates(true) .build(); writer.afterPropertiesSet(); List<Foo> items = new ArrayList<>(1); items.add(new Foo(1, "two", "three")); writer.write(items); } @Test public void testCustomPreparedStatementSetter() throws Exception { JdbcBatchItemWriter<Map<String, Object>> writer = new JdbcBatchItemWriterBuilder<Map<String, Object>>() .itemPreparedStatementSetter((item, ps) -> { ps.setInt(0, (int) item.get("first")); ps.setString(1, (String) item.get("second")); ps.setString(2, (String) item.get("third")); }) .dataSource(this.dataSource) .sql("INSERT INTO FOO (first, second, third) VALUES (:first, :second, :third)") .build(); writer.afterPropertiesSet(); List<Map<String, Object>> items = buildMapItems(); writer.write(items); verifyWrite(); } @Test public void testCustomPSqlParameterSourceProvider() throws Exception { JdbcBatchItemWriter<Map<String, Object>> writer = new JdbcBatchItemWriterBuilder<Map<String, Object>>() .itemSqlParameterSourceProvider(MapSqlParameterSource::new) .dataSource(this.dataSource) .sql("INSERT INTO FOO (first, second, third) VALUES (:first, :second, :third)") .build(); writer.afterPropertiesSet(); List<Map<String, Object>> items = buildMapItems(); writer.write(items); verifyWrite(); } @Test public void testBuildAssertions() { try { new JdbcBatchItemWriterBuilder<Map<String, Object>>() .itemSqlParameterSourceProvider(MapSqlParameterSource::new) .build(); } catch (IllegalStateException ise) { assertEquals("Either a DataSource or a NamedParameterJdbcTemplate is required", ise.getMessage()); } catch (Exception e) { fail("Incorrect exception was thrown when missing DataSource and JdbcTemplate: " + e.getMessage()); } try { new JdbcBatchItemWriterBuilder<Map<String, Object>>() .itemSqlParameterSourceProvider(MapSqlParameterSource::new) .dataSource(this.dataSource) .build(); } catch (IllegalArgumentException ise) { assertEquals("A SQL statement is required", ise.getMessage()); } catch (Exception e) { fail("Incorrect exception was thrown when testing missing SQL: " + e); } try { new JdbcBatchItemWriterBuilder<Map<String, Object>>() .dataSource(this.dataSource) .sql("INSERT INTO FOO VALUES (?, ?, ?)") .columnMapped() .beanMapped() .build(); } catch (IllegalStateException ise) { assertEquals("Either an item can be mapped via db column or via bean spec, can't be both", ise.getMessage()); } catch (Exception e) { fail("Incorrect exception was thrown both mapping types are used" + e.getMessage()); } } private void verifyWrite() { verifyRow(1, "two", "three"); verifyRow(4, "five", "six"); verifyRow(7, "eight", "nine"); } private List<Map<String, Object>> buildMapItems() { List<Map<String, Object>> items = new ArrayList<>(3); Map<String, Object> item = new HashMap<>(3); item.put("first", 1); item.put("second", "two"); item.put("third", "three"); items.add(item); item = new HashMap<>(3); item.put("first", 4); item.put("second", "five"); item.put("third", "six"); items.add(item); item = new HashMap<>(3); item.put("first", 7); item.put("second", "eight"); item.put("third", "nine"); items.add(item); return items; } private void verifyRow(int i, String i1, String nine) { JdbcOperations template = new JdbcTemplate(this.dataSource); assertEquals(1, (int) template.queryForObject( "select count(*) from foo where first = ? and second = ? and third = ?", new Object[] {i, i1, nine}, Integer.class)); } public static class Foo { private int first; private String second; private String third; public Foo(int first, String second, String third) { this.first = first; this.second = second; this.third = 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) ;"; @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()); dataSourceInitializer.setDatabasePopulator(new ResourceDatabasePopulator(create)); return dataSourceInitializer; } } }