diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcPagingItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcPagingItemReader.java index 337b8ebf4..c966d95f3 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcPagingItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcPagingItemReader.java @@ -1,6 +1,22 @@ +/* + * 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.item.database; import org.springframework.batch.item.support.AbstractItemReaderItemStream; +import org.springframework.batch.item.database.support.PagingQueryProvider; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.ClassUtils; import org.springframework.util.Assert; @@ -9,9 +25,6 @@ import org.springframework.jdbc.core.RowCallbackHandler; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; import org.springframework.jdbc.core.simple.ParameterizedRowMapper; -import org.springframework.jdbc.support.JdbcUtils; -import org.springframework.jdbc.support.MetaDataAccessException; -import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -25,12 +38,13 @@ import java.sql.SQLException; * {@link org.springframework.batch.item.ItemReader} for reading database records using JDBC in a paging * fashion. * - * It executes the SQL built from values specified for {@link #setSelectClause(String)} (String)}, - * {@link #setFromClause(String)} (String)} and {@link #setWhereClause(String)} (String)} to retrieve requested data. - * The query is executed using paged requests of a size specified in {@link #setPageSize(int)}. Additional pages - * are requested when needed as {@link #read()} method is called, returning an object corresponding to current position. + * It executes the SQL built by the {@link PagingQueryProvider} to retrieve requested data. + * The query is executed using paged requests of a size specified in {@link #setPageSize(int)}. + * Additional pages are requested when needed as {@link #read()} method is called, returning an + * object corresponding to current position. * - * The performance of the paging depends on the database specific features available to limit the number of returned rows. + * The performance of the paging depends on the database specific features available to limit the number + * of returned rows. * * Setting a fairly large page size and using a commit interval that matches the page size should provide * better performance. @@ -46,22 +60,12 @@ public class JdbcPagingItemReader extends AbstractItemReaderItemStream imp private DataSource dataSource; + private PagingQueryProvider queryProvider; + private SimpleJdbcTemplate simpleJdbcTemplate; private ParameterizedRowMapper parameterizedRowMapper; - private String databaseProductName; - - private String selectClause; - - private String fromClause; - - private String whereClause; - - private String sortKey; - - private String orderClause; - private String firstPageSql; private String remainingPagesSql; @@ -86,53 +90,8 @@ public class JdbcPagingItemReader extends AbstractItemReaderItemStream imp this.dataSource = dataSource; } - /** - * @param selectClause SELECT clause part of SQL query string - */ - public void setSelectClause(String selectClause) { - String keyWord = "select "; - String temp = selectClause.trim(); - if (temp.toLowerCase().startsWith(keyWord) && temp.length() > keyWord.length()) { - this.selectClause = temp.substring(keyWord.length()); - } - else { - this.selectClause = temp; - } - } - - /** - * @param fromClause FROM clause part of SQL query string - */ - public void setFromClause(String fromClause) { - String keyWord = "from "; - String temp = fromClause.trim(); - if (temp.toLowerCase().startsWith(keyWord) && temp.length() > keyWord.length()) { - this.fromClause = temp.substring(keyWord.length()); - } - else { - this.fromClause = temp; - } - } - - /** - * @param whereClause WHERE clause part of SQL query string - */ - public void setWhereClause(String whereClause) { - String keyWord = "where "; - String temp = whereClause.trim(); - if (temp.toLowerCase().startsWith(keyWord) && temp.length() > keyWord.length()) { - this.whereClause = temp.substring(keyWord.length()); - } - else { - this.whereClause = temp; - } - } - - /** - * @param sortKey key to use to sort and limit page content - */ - public void setSortKey(String sortKey) { - this.sortKey = sortKey; + public void setQueryProvider(PagingQueryProvider queryProvider) { + this.queryProvider = queryProvider; } /** @@ -159,59 +118,15 @@ public class JdbcPagingItemReader extends AbstractItemReaderItemStream imp */ public void afterPropertiesSet() throws Exception { Assert.notNull(dataSource); - Assert.hasLength(selectClause, "selectClause must be specified"); - Assert.hasLength(fromClause, "fromClause must be specified"); - Assert.hasLength(sortKey, "sortKey must be specified"); Assert.isTrue(pageSize > 0, "pageSize must be greater than zero"); JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.setMaxRows(pageSize); this.simpleJdbcTemplate = new SimpleJdbcTemplate(jdbcTemplate); - initializeSqlStatements(); + Assert.notNull(queryProvider); + this.firstPageSql = queryProvider.generateFirstPageQuery(pageSize); + this.remainingPagesSql = queryProvider.generateRemainingPagesQuery(pageSize); } - private void initializeSqlStatements() throws MetaDataAccessException { - this.databaseProductName = JdbcUtils.commonDatabaseName( - JdbcUtils.extractDatabaseMetaData(dataSource, "getDatabaseProductName").toString()); - String topClause = ""; - String limitCondition = ""; - String limitClause = ""; - if ("DB2".equals(databaseProductName)) { - limitClause = " FETCH FIRST " + pageSize + " ROWS ONLY"; - } - else if ("Oracle".equals(databaseProductName)) { - limitCondition = "ROWNUM <= " + pageSize; - } - else if ("MySQL".equals(databaseProductName) || "PostgreSQL".equals(databaseProductName)) { - limitClause = " LIMIT " + pageSize; - } - else if ("Microsoft SQL Server".equals(databaseProductName) || "Sybase".equals(databaseProductName) || - "HSQL Database Engine".equals(databaseProductName)) { - topClause = "TOP " + pageSize + " "; - } - else if ("Apache Derby".equals(databaseProductName)) { - String version = JdbcUtils.extractDatabaseMetaData(dataSource, "getDatabaseVersion").toString(); - if ("10.4.1.3".compareTo(version) > 0) { - throw new InvalidDataAccessResourceUsageException(databaseProductName + " version " + version + " is not supported"); - } - // Derby doesn't support TOP or LIMIT -- maxRows will limit the rows retrieved - } - else { - throw new InvalidDataAccessResourceUsageException(databaseProductName + " is not a supported database"); - } - this.orderClause = " ORDER BY SORT_KEY"; - this.firstPageSql = "SELECT " + topClause + selectClause + ", " + sortKey + " AS SORT_KEY" + - " FROM " + fromClause + - (whereClause == null ? "" : " WHERE " + whereClause) + - (limitCondition.length() == 0 ? "" : (whereClause == null ? " WHERE " : " AND ") + limitCondition) + - orderClause + limitClause; - this.remainingPagesSql = "SELECT " + topClause + selectClause + ", " + sortKey + " AS SORT_KEY" + - " FROM " + fromClause + " WHERE " + sortKey + " > ?" + - (whereClause == null ? "" : " AND " + whereClause) + - (limitCondition.length() == 0 ? "" : " AND " + limitCondition) + - orderClause + limitClause; - } - - @Override @SuppressWarnings("unchecked") protected T doRead() throws Exception { @@ -289,35 +204,13 @@ public class JdbcPagingItemReader extends AbstractItemReaderItemStream imp page = itemIndex / pageSize; current = itemIndex % pageSize; - int offset = (page * pageSize) - 1; - int lastRowNum = (page * pageSize); - logger.debug("Jumping to page " + page + " and index " + current); if (page > 0) { - String windowClause = ""; - String topClause = ""; - String limitClause = ""; - if ("DB2".equals(databaseProductName) || "Oracle".equals(databaseProductName) || - "Microsoft SQL Server".equals(databaseProductName) || "Sybase".equals(databaseProductName) || - "Apache Derby".equals(databaseProductName)) { - windowClause = "ROW_NUMBER() OVER (ORDER BY " + sortKey + " ASC) AS ROW_NUMBER"; - } - else if ("HSQL Database Engine".equals(databaseProductName)) { - topClause = "LIMIT " + offset + " 1 "; - } - else if ("MySQL".equals(databaseProductName) || "PostgreSQL".equals(databaseProductName) || - "HSQL Database Engine".equals(databaseProductName)) { - limitClause = " LIMIT 1 OFFSET " + offset; - } + String jumpToItemSql; - String jumpToItemSql = - (windowClause.length() > 0 ? "SELECT * FROM ( " : "") + - "SELECT " + (topClause.length() > 0 ? topClause : "") + sortKey + " AS SORT_KEY" + - (windowClause.length() > 0 ? ", " + windowClause : "") + - " FROM " + fromClause + (whereClause == null ? "" : " WHERE " + whereClause) + - (windowClause.length() > 0 ? ") WHERE ROW_NUMBER = " + lastRowNum : orderClause + limitClause); + jumpToItemSql = queryProvider.generateJumpToItemQuery(itemIndex, pageSize); if (logger.isDebugEnabled()) { logger.debug("SQL used for jumping: [" + jumpToItemSql + "]"); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProvider.java new file mode 100644 index 000000000..0cf3fe07a --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProvider.java @@ -0,0 +1,147 @@ +/* + * 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.item.database.support; + +import org.springframework.util.Assert; +import org.springframework.jdbc.support.MetaDataAccessException; +import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.dao.InvalidDataAccessResourceUsageException; + +import javax.sql.DataSource; + +/** + * Abstract SQL Paging Query Provider to serve as a base class for all provided SQL paging query providers. + * + * Any implementation must provide a way to specify the select clause, from clause and optionally a where clause. + * In addition a way to specify a single column sort key must also be provided. This sort key will be used to + * provide the paging functinality. It is recommended that there should be an index for the sort key to provide + * better performance. + * + * Provides properties and preperation for the mandatory "selectClause" and "fromClause" as well as for the + * optional "whereClause". Also provides property for the mandatory "sortKey". + * + * @author Thomas Risberg + * @since 2.0 + */ +public abstract class AbstractSqlPagingQueryProvider implements PagingQueryProvider { + + private String selectClause; + + private String fromClause; + + private String whereClause; + + private String sortKey; + + /** + * @param selectClause SELECT clause part of SQL query string + */ + public void setSelectClause(String selectClause) { + String keyWord = "select "; + String temp = selectClause.trim(); + if (temp.toLowerCase().startsWith(keyWord) && temp.length() > keyWord.length()) { + this.selectClause = temp.substring(keyWord.length()); + } + else { + this.selectClause = temp; + } + } + + /** + * + * @return the + */ + protected String getSelectClause() { + return selectClause; + } + + /** + * @param fromClause FROM clause part of SQL query string + */ + public void setFromClause(String fromClause) { + String keyWord = "from "; + String temp = fromClause.trim(); + if (temp.toLowerCase().startsWith(keyWord) && temp.length() > keyWord.length()) { + this.fromClause = temp.substring(keyWord.length()); + } + else { + this.fromClause = temp; + } + } + + /** + * + * @return + */ + protected String getFromClause() { + return fromClause; + } + + /** + * @param whereClause WHERE clause part of SQL query string + */ + public void setWhereClause(String whereClause) { + String keyWord = "where "; + String temp = whereClause.trim(); + if (temp.toLowerCase().startsWith(keyWord) && temp.length() > keyWord.length()) { + this.whereClause = temp.substring(keyWord.length()); + } + else { + this.whereClause = temp; + } + } + + /** + * + * @return + */ + protected String getWhereClause() { + return whereClause; + } + + /** + * @param sortKey key to use to sort and limit page content + */ + public void setSortKey(String sortKey) { + this.sortKey = sortKey; + } + + /** + * + * @return + */ + protected String getSortKey() { + return sortKey; + } + + /** + * Check mandatory properties. + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void init(DataSource dataSource) throws Exception { + Assert.notNull(dataSource); + Assert.hasLength(selectClause, "selectClause must be specified"); + Assert.hasLength(fromClause, "fromClause must be specified"); + Assert.hasLength(sortKey, "sortKey must be specified"); + } + + public abstract String generateFirstPageQuery(int pageSize); + + public abstract String generateRemainingPagesQuery(int pageSize); + + public abstract String generateJumpToItemQuery(int itemIndex, int pageSize); + +} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java new file mode 100644 index 000000000..83d1867de --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java @@ -0,0 +1,40 @@ +/* + * 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.item.database.support; + +import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.dao.InvalidDataAccessResourceUsageException; + +import javax.sql.DataSource; + +/** + * Derby implementation of a {@link PagingQueryProvider} using standard SQL:2003 windowing functions. + * These features are supported starting with Apache Derby version 10.4.1.3. + * + * @author Thomas Risberg + * @since 2.0 + */ +public class DerbyPagingQueryProvider extends SqlWindowingPagingQueryProvider { + + @Override + public void init(DataSource dataSource) throws Exception { + super.init(dataSource); + String version = JdbcUtils.extractDatabaseMetaData(dataSource, "getDatabaseProductVersion").toString(); + if ("10.4.1.3".compareTo(version) > 0) { + throw new InvalidDataAccessResourceUsageException("Apache Derby version " + version + " is not supported by this class, Only version 10.4.1.3 or later is supported"); + } + } +} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HsqlPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HsqlPagingQueryProvider.java new file mode 100644 index 000000000..d5f02940a --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HsqlPagingQueryProvider.java @@ -0,0 +1,60 @@ +/* + * 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.item.database.support; + +/** + * MySQL implementation of a {@link org.springframework.batch.item.database.support.PagingQueryProvider} using database specific features. + * + * @author Thomas Risberg + * @since 2.0 + */ +public class HsqlPagingQueryProvider extends AbstractSqlPagingQueryProvider { + + @Override + public String generateFirstPageQuery(int pageSize) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ").append("TOP ").append(pageSize).append(" ").append(getSelectClause()); + sql.append(" FROM ").append(getFromClause()); + sql.append(getWhereClause() == null ? "" : " WHERE " + getWhereClause()); + + return sql.toString(); + } + + @Override + public String generateRemainingPagesQuery(int pageSize) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ").append("TOP ").append(pageSize).append(" ").append(getSelectClause()); + sql.append(" FROM ").append(getFromClause()); + sql.append(" WHERE ").append(getSortKey()).append(" > ?"); + sql.append(getWhereClause() == null ? "" : " AND " + getWhereClause()); + + return sql.toString(); + } + + @Override + public String generateJumpToItemQuery(int itemIndex, int pageSize) { + int page = itemIndex / pageSize; + int offset = (page * pageSize) - 1; + + StringBuilder sql = new StringBuilder(); + sql.append("SELECT LIMIT ").append(offset).append(" 1 ").append(getSortKey()).append(" AS SORT_KEY"); + sql.append(" FROM ").append(getFromClause()); + sql.append(getWhereClause() == null ? "" : " WHERE " + getWhereClause()); + + return sql.toString(); + } + +} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/MySqlPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/MySqlPagingQueryProvider.java new file mode 100644 index 000000000..27937d88e --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/MySqlPagingQueryProvider.java @@ -0,0 +1,62 @@ +/* + * 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.item.database.support; + +/** + * MySQL implementation of a {@link PagingQueryProvider} using database specific features. + * + * @author Thomas Risberg + * @since 2.0 + */ +public class MySqlPagingQueryProvider extends AbstractSqlPagingQueryProvider { + + @Override + public String generateFirstPageQuery(int pageSize) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ").append(getSelectClause()); + sql.append(" FROM ").append(getFromClause()); + sql.append(getWhereClause() == null ? "" : " WHERE " + getWhereClause()); + sql.append(" LIMIT ").append(pageSize); + + return sql.toString(); + } + + @Override + public String generateRemainingPagesQuery(int pageSize) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ").append(getSelectClause()); + sql.append(" FROM ").append(getFromClause()); + sql.append(" WHERE ").append(getSortKey()).append(" > ?"); + sql.append(getWhereClause() == null ? "" : " AND " + getWhereClause()); + sql.append(" LIMIT ").append(pageSize); + + return sql.toString(); + } + + @Override + public String generateJumpToItemQuery(int itemIndex, int pageSize) { + int page = itemIndex / pageSize; + int offset = (page * pageSize) - 1; + + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ").append(getSortKey()).append(" AS SORT_KEY"); + sql.append(" FROM ").append(getFromClause()).append(getWhereClause() == null ? "" : " WHERE " + getWhereClause()); + sql.append(" LIMIT ").append(offset).append(" 1"); + + return sql.toString(); + } + +} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/JdbcPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/PagingQueryProvider.java similarity index 50% rename from spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/JdbcPagingQueryProvider.java rename to spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/PagingQueryProvider.java index 21e749d3b..245e6ae32 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/JdbcPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/PagingQueryProvider.java @@ -15,41 +15,39 @@ */ package org.springframework.batch.item.database.support; +import javax.sql.DataSource; + /** - * Interface defining the functionality to be provided for generating paging queries for use with JDBC. - * - * Any usage must provide the select clause, from clause and optionally a where clause. In addition a - * single column sort key must be defined. This sort key will be used to provide the paging functinality. - * It is recommended that there should be an index for the sort key to provide better performance. + * Interface defining the functionality to be provided for generating paging queries for use with Paging + * Item Readers. * * @author Thomas Risberg * @since 2.0 */ -public interface JdbcPagingQueryProvider { +public interface PagingQueryProvider { + + /** + * Initialize the query provider using the provided {@link DataSource} if necessary. + * + * @param dataSource DataSource to use for any initialization + */ + void init(DataSource dataSource) throws Exception; /** * Generate the query that will provide the first page, limited by the page size. * * @param pageSize number of rows to read for each page - * @param selectClause the columns that are part of the selct clause - * @param fromClause the table(s) that are part of the from clause - * @param whereClause the conditions that are part of the where clause - * @param sortKey the single column used for sorting * @return the generated query */ - String generateFirstPageQuery(int pageSize, String selectClause, String fromClause, String whereClause, String sortKey); + String generateFirstPageQuery(int pageSize); /** * Generate the query that will provide the first page, limited by the page size. * * @param pageSize number of rows to read for each page - * @param selectClause the columns that are part of the selct clause - * @param fromClause the table(s) that are part of the from clause - * @param whereClause the conditions that are part of the where clause - * @param sortKey the single column used for sorting * @return the generated query */ - String generateRemainingPagesQuery(int pageSize, String selectClause, String fromClause, String whereClause, String sortKey); + String generateRemainingPagesQuery(int pageSize); /** * @@ -59,12 +57,8 @@ public interface JdbcPagingQueryProvider { * * @param itemIndex the index for the next item to be read * @param pageSize number of rows to read for each page - * @param selectClause the columns that are part of the selct clause - * @param fromClause the table(s) that are part of the from clause - * @param whereClause the conditions that are part of the where clause - * @param sortKey the single column used for sorting * @return the generated query */ - String generateJumpToItemQuery(int itemIndex, int pageSize, String selectClause, String fromClause, String whereClause, String sortKey); + String generateJumpToItemQuery(int itemIndex, int pageSize); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java index ec978a572..977e102ca 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java @@ -22,40 +22,40 @@ package org.springframework.batch.item.database.support; * @author Thomas Risberg * @since 2.0 */ -public class SqlWindowingPagingQueryProvider implements JdbcPagingQueryProvider { +public class SqlWindowingPagingQueryProvider extends AbstractSqlPagingQueryProvider { - public String generateFirstPageQuery(int pageSize, String selectClause, String fromClause, String whereClause, String sortKey) { + public String generateFirstPageQuery(int pageSize) { StringBuilder sql = new StringBuilder(); sql.append("SELECT * FROM ( "); - sql.append("SELECT ").append(selectClause).append(", "); - sql.append("ROW_NUMBER() OVER (ORDER BY ").append(sortKey).append(" ASC) AS ROW_NUMBER"); - sql.append(" FROM ").append(fromClause).append(whereClause == null ? "" : " WHERE " + whereClause); + sql.append("SELECT ").append(getSelectClause()).append(", "); + sql.append("ROW_NUMBER() OVER (ORDER BY ").append(getSortKey()).append(" ASC) AS ROW_NUMBER"); + sql.append(" FROM ").append(getFromClause()).append(getWhereClause() == null ? "" : " WHERE " + getWhereClause()); sql.append(") WHERE ROW_NUMBER <= ").append(pageSize); return sql.toString(); } - public String generateRemainingPagesQuery(int pageSize, String selectClause, String fromClause, String whereClause, String sortKey) { + public String generateRemainingPagesQuery(int pageSize) { StringBuilder sql = new StringBuilder(); sql.append("SELECT * FROM ( "); - sql.append("SELECT ").append(selectClause).append(", "); - sql.append("ROW_NUMBER() OVER (ORDER BY ").append(sortKey).append(" ASC) AS ROW_NUMBER"); - sql.append(" FROM ").append(fromClause).append(" WHERE ").append(sortKey).append(" > ?"); - sql.append(whereClause == null ? "" : " AND " + whereClause); + sql.append("SELECT ").append(getSelectClause()).append(", "); + sql.append("ROW_NUMBER() OVER (ORDER BY ").append(getSortKey()).append(" ASC) AS ROW_NUMBER"); + sql.append(" FROM ").append(getFromClause()).append(" WHERE ").append(getSortKey()).append(" > ?"); + sql.append(getWhereClause() == null ? "" : " AND " + getWhereClause()); sql.append(") WHERE ROW_NUMBER <= ").append(pageSize); return sql.toString(); } - public String generateJumpToItemQuery(int itemIndex, int pageSize, String selectClause, String fromClause, String whereClause, String sortKey) { + public String generateJumpToItemQuery(int itemIndex, int pageSize) { int page = itemIndex / pageSize; int lastRowNum = (page * pageSize); StringBuilder sql = new StringBuilder(); sql.append("SELECT SORT_KEY FROM ( "); - sql.append("SELECT ").append(sortKey).append(" AS SORT_KEY, "); - sql.append("ROW_NUMBER() OVER (ORDER BY ").append(sortKey).append(" ASC) AS ROW_NUMBER"); - sql.append(" FROM ").append(fromClause).append(whereClause == null ? "" : " WHERE " + whereClause); + sql.append("SELECT ").append(getSortKey()).append(" AS SORT_KEY, "); + sql.append("ROW_NUMBER() OVER (ORDER BY ").append(getSortKey()).append(" ASC) AS ROW_NUMBER"); + sql.append(" FROM ").append(getFromClause()).append(getWhereClause() == null ? "" : " WHERE " + getWhereClause()); sql.append(") WHERE ROW_NUMBER = ").append(lastRowNum); return sql.toString(); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcPagingItemReaderCommonTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcPagingItemReaderCommonTests.java index a06970f6a..7fd35524f 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcPagingItemReaderCommonTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcPagingItemReaderCommonTests.java @@ -6,6 +6,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.batch.item.CommonItemStreamItemReaderTests; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.database.support.HsqlPagingQueryProvider; import org.springframework.batch.item.sample.Foo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.simple.ParameterizedRowMapper; @@ -24,10 +25,12 @@ public class JdbcPagingItemReaderCommonTests extends CommonItemStreamItemReaderT protected ItemReader getItemReader() throws Exception { JdbcPagingItemReader reader = new JdbcPagingItemReader(); - reader.setSelectClause("select ID, NAME, VALUE"); - reader.setFromClause("from T_FOOS"); - reader.setSortKey("ID"); reader.setDataSource(dataSource); + HsqlPagingQueryProvider queryProvider = new HsqlPagingQueryProvider(); + queryProvider.setSelectClause("select ID, NAME, VALUE"); + queryProvider.setFromClause("from T_FOOS"); + queryProvider.setSortKey("ID"); + reader.setQueryProvider(queryProvider); reader.setParameterizedRowMapper( new ParameterizedRowMapper() { public Foo mapRow(ResultSet rs, int i) throws SQLException { @@ -49,10 +52,13 @@ public class JdbcPagingItemReaderCommonTests extends CommonItemStreamItemReaderT protected void pointToEmptyInput(ItemReader tested) throws Exception { JdbcPagingItemReader reader = (JdbcPagingItemReader) tested; reader.close(new ExecutionContext()); - reader.setSelectClause("select ID, NAME, VALUE"); - reader.setFromClause("from T_FOOS"); - reader.setWhereClause("where id = -1"); reader.setDataSource(dataSource); + HsqlPagingQueryProvider queryProvider = new HsqlPagingQueryProvider(); + queryProvider.setSelectClause("select ID, NAME, VALUE"); + queryProvider.setFromClause("from T_FOOS"); + queryProvider.setWhereClause("where ID = -1"); + queryProvider.setSortKey("ID"); + reader.setQueryProvider(queryProvider); reader.setPageSize(3); reader.afterPropertiesSet(); reader.open(new ExecutionContext()); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcPagingItemReaderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcPagingItemReaderIntegrationTests.java index 23f24fd3e..d235f1774 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcPagingItemReaderIntegrationTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcPagingItemReaderIntegrationTests.java @@ -5,6 +5,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.ContextConfiguration; import org.springframework.batch.item.sample.Foo; import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.database.support.HsqlPagingQueryProvider; import org.springframework.jdbc.core.simple.ParameterizedRowMapper; import java.sql.ResultSet; @@ -22,10 +23,12 @@ public class JdbcPagingItemReaderIntegrationTests extends AbstractDataSourceItem protected ItemReader createItemReader() throws Exception { JdbcPagingItemReader inputSource = new JdbcPagingItemReader(); - inputSource.setSelectClause("select ID, NAME, VALUE"); - inputSource.setFromClause("from T_FOOS"); - inputSource.setSortKey("ID"); inputSource.setDataSource(dataSource); + HsqlPagingQueryProvider queryProvider = new HsqlPagingQueryProvider(); + queryProvider.setSelectClause("select ID, NAME, VALUE"); + queryProvider.setFromClause("from T_FOOS"); + queryProvider.setSortKey("ID"); + inputSource.setQueryProvider(queryProvider); inputSource.setParameterizedRowMapper( new ParameterizedRowMapper() { public Foo mapRow(ResultSet rs, int i) throws SQLException { diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProviderTests.java new file mode 100644 index 000000000..c25f85f76 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProviderTests.java @@ -0,0 +1,52 @@ +/* + * 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.item.database.support; + +import org.junit.Before; +import org.junit.Test; + +/** + * @author Thomas Risberg + */ +public abstract class AbstractSqlPagingQueryProviderTests { + + protected AbstractSqlPagingQueryProvider pagingQueryProvider; + protected int pageSize; + + + @Before + public void onSetUp() { + if (pagingQueryProvider == null) { + throw new IllegalArgumentException("pagingQuery{rovider can't be null"); + } + pagingQueryProvider.setSelectClause("id, name, age"); + pagingQueryProvider.setFromClause("foo"); + pagingQueryProvider.setWhereClause("bar = 1"); + pagingQueryProvider.setSortKey("id"); + pageSize = 100; + + } + + @Test + public abstract void testGenerateFirstPageQuery(); + + @Test + public abstract void testGenerateRemainingPagesQuery(); + + @Test + public abstract void testGenerateJumpToItemQuery(); + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java new file mode 100644 index 000000000..d80327ce6 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java @@ -0,0 +1,102 @@ +/* + * 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.item.database.support; + +import static org.junit.Assert.fail; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; + +import org.junit.Test; +import org.junit.Assert; +import org.springframework.dao.InvalidDataAccessResourceUsageException; + +import javax.sql.DataSource; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.sql.Connection; + +/** + * @author Thomas Risberg + */ +public class DerbyPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests { + + public DerbyPagingQueryProviderTests() { + pagingQueryProvider = new DerbyPagingQueryProvider(); + } + + @Test + public void testInit() throws Exception { + DataSource ds = createMock(DataSource.class); + Connection con = createMock(Connection.class); + DatabaseMetaData dmd = createMock(DatabaseMetaData.class); + expect(dmd.getDatabaseProductVersion()).andReturn("10.4.1.3"); + expect(con.getMetaData()).andReturn(dmd); + expect(ds.getConnection()).andReturn(con); + replay(dmd); + replay(con); + replay(ds); + pagingQueryProvider.init(ds); + verify(ds); + verify(con); + verify(dmd); + } + + @Test + public void testInitWithUnsupportedVErsion() throws Exception { + DataSource ds = createMock(DataSource.class); + Connection con = createMock(Connection.class); + DatabaseMetaData dmd = createMock(DatabaseMetaData.class); + expect(dmd.getDatabaseProductVersion()).andReturn("10.2.9.9"); + expect(con.getMetaData()).andReturn(dmd); + expect(ds.getConnection()).andReturn(con); + replay(dmd); + replay(con); + replay(ds); + try { + pagingQueryProvider.init(ds); + fail(); + } catch (InvalidDataAccessResourceUsageException e) { + // expected + } + verify(ds); + verify(con); + verify(dmd); + } + + @Test + @Override + public void testGenerateFirstPageQuery() { + String sql = "SELECT * FROM ( SELECT id, name, age, ROW_NUMBER() OVER (ORDER BY id ASC) AS ROW_NUMBER FROM foo WHERE bar = 1) WHERE ROW_NUMBER <= 100"; + String s = pagingQueryProvider.generateFirstPageQuery(pageSize); + Assert.assertEquals("", sql, s); + } + + @Test @Override + public void testGenerateRemainingPagesQuery() { + String sql = "SELECT * FROM ( SELECT id, name, age, ROW_NUMBER() OVER (ORDER BY id ASC) AS ROW_NUMBER FROM foo WHERE id > ? AND bar = 1) WHERE ROW_NUMBER <= 100"; + String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); + Assert.assertEquals("", sql, s); + } + + @Test @Override + public void testGenerateJumpToItemQuery() { + String sql = "SELECT SORT_KEY FROM ( SELECT id AS SORT_KEY, ROW_NUMBER() OVER (ORDER BY id ASC) AS ROW_NUMBER FROM foo WHERE bar = 1) WHERE ROW_NUMBER = 100"; + String s = pagingQueryProvider.generateJumpToItemQuery(145, pageSize); + Assert.assertEquals("", sql, s); + } +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HsqlPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HsqlPagingQueryProviderTests.java new file mode 100644 index 000000000..dd2877d1f --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HsqlPagingQueryProviderTests.java @@ -0,0 +1,51 @@ +/* + * 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.item.database.support; + +import org.junit.Test; +import org.junit.Assert; + +/** + * @author Thomas Risberg + */ +public class HsqlPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests { + + public HsqlPagingQueryProviderTests() { + pagingQueryProvider = new HsqlPagingQueryProvider(); + } + + @Test + @Override + public void testGenerateFirstPageQuery() { + String sql = "SELECT TOP 100 id, name, age FROM foo WHERE bar = 1"; + String s = pagingQueryProvider.generateFirstPageQuery(pageSize); + Assert.assertEquals("", sql, s); + } + + @Test @Override + public void testGenerateRemainingPagesQuery() { + String sql = "SELECT TOP 100 id, name, age FROM foo WHERE id > ? AND bar = 1"; + String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); + Assert.assertEquals("", sql, s); + } + + @Test @Override + public void testGenerateJumpToItemQuery() { + String sql = "SELECT LIMIT 99 1 id AS SORT_KEY FROM foo WHERE bar = 1"; + String s = pagingQueryProvider.generateJumpToItemQuery(145, pageSize); + Assert.assertEquals("", sql, s); + } +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MySqlPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MySqlPagingQueryProviderTests.java new file mode 100644 index 000000000..486153b74 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MySqlPagingQueryProviderTests.java @@ -0,0 +1,51 @@ +/* + * 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.item.database.support; + +import org.junit.Test; +import org.junit.Assert; + +/** + * @author Thomas Risberg + */ +public class MySqlPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests { + + public MySqlPagingQueryProviderTests() { + pagingQueryProvider = new MySqlPagingQueryProvider(); + } + + @Test + @Override + public void testGenerateFirstPageQuery() { + String sql = "SELECT id, name, age FROM foo WHERE bar = 1 LIMIT 100"; + String s = pagingQueryProvider.generateFirstPageQuery(pageSize); + Assert.assertEquals("", sql, s); + } + + @Test @Override + public void testGenerateRemainingPagesQuery() { + String sql = "SELECT id, name, age FROM foo WHERE id > ? AND bar = 1 LIMIT 100"; + String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); + Assert.assertEquals("", sql, s); + } + + @Test @Override + public void testGenerateJumpToItemQuery() { + String sql = "SELECT id AS SORT_KEY FROM foo WHERE bar = 1 LIMIT 99 1"; + String s = pagingQueryProvider.generateJumpToItemQuery(145, pageSize); + Assert.assertEquals("", sql, s); + } +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java index 6ae0d8627..e8d99156c 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java @@ -18,37 +18,35 @@ package org.springframework.batch.item.database.support; import static org.junit.Assert.*; import org.junit.Test; +import org.junit.Before; /** * @author Thomas Risberg */ -public class SqlWindowingPagingQueryProviderTests { +public class SqlWindowingPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests { - JdbcPagingQueryProvider pagingQueryProvider = new SqlWindowingPagingQueryProvider(); - private int pageSize = 100; - private String selectClause = "id, name, age"; - private String fromClause = "foo"; - private String whereClaues = "bar = 1"; - private String sortKey = "id"; + public SqlWindowingPagingQueryProviderTests() { + pagingQueryProvider = new SqlWindowingPagingQueryProvider(); + } - @Test + @Test @Override public void testGenerateFirstPageQuery() { String sql = "SELECT * FROM ( SELECT id, name, age, ROW_NUMBER() OVER (ORDER BY id ASC) AS ROW_NUMBER FROM foo WHERE bar = 1) WHERE ROW_NUMBER <= 100"; - String s = pagingQueryProvider.generateFirstPageQuery(pageSize, selectClause, fromClause, whereClaues, sortKey); + String s = pagingQueryProvider.generateFirstPageQuery(pageSize); assertEquals("", sql, s); } - @Test + @Test @Override public void testGenerateRemainingPagesQuery() { String sql = "SELECT * FROM ( SELECT id, name, age, ROW_NUMBER() OVER (ORDER BY id ASC) AS ROW_NUMBER FROM foo WHERE id > ? AND bar = 1) WHERE ROW_NUMBER <= 100"; - String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize, selectClause, fromClause, whereClaues, sortKey); + String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); assertEquals("", sql, s); } - @Test + @Test @Override public void testGenerateJumpToItemQuery() { String sql = "SELECT SORT_KEY FROM ( SELECT id AS SORT_KEY, ROW_NUMBER() OVER (ORDER BY id ASC) AS ROW_NUMBER FROM foo WHERE bar = 1) WHERE ROW_NUMBER = 100"; - String s = pagingQueryProvider.generateJumpToItemQuery(145, pageSize, selectClause, fromClause, whereClaues, sortKey); + String s = pagingQueryProvider.generateJumpToItemQuery(145, pageSize); assertEquals("", sql, s); } }