From ecc19e96ffd3abd37207a699cbeb150b52b89db4 Mon Sep 17 00:00:00 2001 From: trisberg Date: Fri, 22 Aug 2008 19:51:56 +0000 Subject: [PATCH] BATCH-760: added a JdbcPagingQueryProvider abstraction and an SQL:2003 windowing functions implementation --- .../support/JdbcPagingQueryProvider.java | 70 +++++++++++++++++++ .../SqlWindowingPagingQueryProvider.java | 64 +++++++++++++++++ .../SqlWindowingPagingQueryProviderTests.java | 54 ++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/JdbcPagingQueryProvider.java create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java 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/JdbcPagingQueryProvider.java new file mode 100644 index 000000000..21e749d3b --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/JdbcPagingQueryProvider.java @@ -0,0 +1,70 @@ +/* + * 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; + +/** + * 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. + * + * @author Thomas Risberg + * @since 2.0 + */ +public interface JdbcPagingQueryProvider { + + /** + * 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); + + /** + * 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); + + /** + * + * Generate the query that will provide the jump to item query. The itemIndex provided could be in the middle of + * the page and together with the page size it will be used to calculate the last index of the preceding page + * to be able to retrieve the sort key for this row. + * + * @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); + +} 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 new file mode 100644 index 000000000..ec978a572 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java @@ -0,0 +1,64 @@ +/* + * 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; + +/** + * Generic Paging Query Provider using standard SQL:2003 windowing functions. These features are supported by + * DB2, Oracle, SQL Server 2005, Sybase and Apache Derby version 10.4.1.3 + * + * @author Thomas Risberg + * @since 2.0 + */ +public class SqlWindowingPagingQueryProvider implements JdbcPagingQueryProvider { + + public String generateFirstPageQuery(int pageSize, String selectClause, String fromClause, String whereClause, String sortKey) { + 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(") WHERE ROW_NUMBER <= ").append(pageSize); + + return sql.toString(); + } + + public String generateRemainingPagesQuery(int pageSize, String selectClause, String fromClause, String whereClause, String sortKey) { + 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(") WHERE ROW_NUMBER <= ").append(pageSize); + + return sql.toString(); + } + + public String generateJumpToItemQuery(int itemIndex, int pageSize, String selectClause, String fromClause, String whereClause, String sortKey) { + 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(") WHERE ROW_NUMBER = ").append(lastRowNum); + + return sql.toString(); + } + +} 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 new file mode 100644 index 000000000..6ae0d8627 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java @@ -0,0 +1,54 @@ +/* + * 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.*; + +import org.junit.Test; + +/** + * @author Thomas Risberg + */ +public class SqlWindowingPagingQueryProviderTests { + + 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"; + + @Test + 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); + assertEquals("", sql, s); + } + + @Test + 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); + assertEquals("", sql, s); + } + + @Test + 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); + assertEquals("", sql, s); + } +}