From cb62d3d14bafb55ce1865798c1d1cd7573334420 Mon Sep 17 00:00:00 2001 From: KyeongHoon Lee Date: Thu, 9 Jan 2025 12:49:29 +0900 Subject: [PATCH 1/5] remove repeating "used for" in comment Signed-off-by: KyeongHoon Lee --- .../batch/item/database/support/SqlPagingQueryUtils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java index 2ae66e4388..5d45c78efc 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2024 the original author or authors. + * Copyright 2006-2025 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. @@ -32,6 +32,7 @@ * @author Michael Minella * @author Mahmoud Ben Hassine * @author Taeik Lim + * @author Kyeonghoon Lee * @since 2.0 */ public abstract class SqlPagingQueryUtils { @@ -233,7 +234,7 @@ public static String generateRowNumSqlQuery(AbstractSqlPagingQueryProvider provi /** * Generates ORDER BY attributes based on the sort keys. - * @param provider the {@link AbstractSqlPagingQueryProvider} to be used for used for + * @param provider the {@link AbstractSqlPagingQueryProvider} to be used for * pagination. * @return a String that can be appended to an ORDER BY clause. */ From 910ca1d6cbb5465734f67e9e7b30f7979a6dee4f Mon Sep 17 00:00:00 2001 From: KyeongHoon Lee Date: Thu, 9 Jan 2025 13:00:42 +0900 Subject: [PATCH 2/5] Add `@FunctionalInterface` to ChunkProcessor Signed-off-by: KyeongHoon Lee --- .../springframework/batch/core/step/item/ChunkProcessor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java index 3bab818b81..b0eb395ca9 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2025 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. @@ -22,8 +22,10 @@ /** * Interface defined for processing {@link org.springframework.batch.item.Chunk}s. * + * @author Kyeonghoon Lee * @since 2.0 */ +@FunctionalInterface public interface ChunkProcessor { void process(StepContribution contribution, Chunk chunk) throws Exception; From 43d786d77b6af2b5b6d40d4caeca2a83ecf3de80 Mon Sep 17 00:00:00 2001 From: KyeongHoon Lee Date: Thu, 9 Jan 2025 13:06:24 +0900 Subject: [PATCH 3/5] Update `SqlPagingQueryUtils` Remove table aliases of the sort keys when the PagingQueryProvider uses sort keys with table aliases and a GROUP BY clause. Signed-off-by: KyeongHoon Lee --- .../database/support/SqlPagingQueryUtils.java | 22 +++++++++++++++++-- .../MySqlPagingQueryProviderTests.java | 20 ++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java index 5d45c78efc..e25429652b 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java @@ -109,8 +109,8 @@ public static String generateLimitGroupedSqlQuery(AbstractSqlPagingQueryProvider buildGroupByClause(provider, sql); sql.append(") AS MAIN_QRY "); sql.append("WHERE "); - buildSortConditions(provider, sql); - sql.append(" ORDER BY ").append(buildSortClause(provider)); + buildSortConditionsWithoutTableAliases(provider, sql); + sql.append(" ORDER BY ").append(buildSortClause(provider.getSortKeysWithoutAliases())); sql.append(" ").append(limitClause); return sql.toString(); @@ -270,6 +270,19 @@ public static String buildSortClause(Map sortKeys) { return builder.toString(); } + /** + * Appends the where conditions required to query for the subsequent pages, without + * using table aliases in sort keys. + * @param provider the {@link AbstractSqlPagingQueryProvider} to be used for + * pagination. + * @param sql {@link StringBuilder} containing the sql to be used for the query. + */ + public static void buildSortConditionsWithoutTableAliases(AbstractSqlPagingQueryProvider provider, + StringBuilder sql) { + List> keys = new ArrayList<>(provider.getSortKeysWithoutAliases().entrySet()); + buildDetailSortConditions(keys, provider, sql); + } + /** * Appends the where conditions required to query for the subsequent pages. * @param provider the {@link AbstractSqlPagingQueryProvider} to be used for @@ -278,6 +291,11 @@ public static String buildSortClause(Map sortKeys) { */ public static void buildSortConditions(AbstractSqlPagingQueryProvider provider, StringBuilder sql) { List> keys = new ArrayList<>(provider.getSortKeys().entrySet()); + buildDetailSortConditions(keys, provider, sql); + } + + private static void buildDetailSortConditions(List> keys, + AbstractSqlPagingQueryProvider provider, StringBuilder sql) { List clauses = new ArrayList<>(); for (int i = 0; i < keys.size(); i++) { 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 index 5c3280a5f9..260443145f 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2025 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. @@ -17,6 +17,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.LinkedHashMap; import org.junit.jupiter.api.Test; @@ -27,6 +28,7 @@ /** * @author Thomas Risberg * @author Michael Minella + * @author Kyeonghoon Lee */ class MySqlPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests { @@ -68,6 +70,22 @@ void testGenerateRemainingPagesQueryWithGroupBy() { assertEquals(sql, s); } + @Test + void testGenerateRemainingPagesQueryWithGroupByWithAlias() { + pagingQueryProvider.setSelectClause("SELECT f.id, f.name, f.age"); + pagingQueryProvider.setFromClause("FROM foo f"); + pagingQueryProvider.setWhereClause("f.bar = 1"); + pagingQueryProvider.setGroupClause("dep"); + Map sortKeys = new LinkedHashMap<>(); + sortKeys.put("f.id", Order.ASCENDING); + pagingQueryProvider.setSortKeys(sortKeys); + + String sql = "SELECT * FROM (SELECT f.id, f.name, f.age FROM foo f WHERE f.bar = 1 GROUP BY dep) AS MAIN_QRY WHERE ((id > ?)) ORDER BY id ASC LIMIT " + + pageSize; + String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); + assertEquals(sql, s); + } + @Test void testFirstPageSqlWithAliases() { Map sorts = new HashMap<>(); From 888cc86c93ed694187139f91ee81f74547c1ef6f Mon Sep 17 00:00:00 2001 From: KyeongHoon Lee Date: Wed, 26 Feb 2025 21:00:59 +0900 Subject: [PATCH 4/5] Revert "Add `@FunctionalInterface` to ChunkProcessor" This reverts commit a851d7143bcd23de33f923b6d6592bb7d63450b8. Signed-off-by: KyeongHoon Lee --- .../springframework/batch/core/step/item/ChunkProcessor.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java index b0eb395ca9..3bab818b81 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2025 the original author or authors. + * Copyright 2006-2022 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. @@ -22,10 +22,8 @@ /** * Interface defined for processing {@link org.springframework.batch.item.Chunk}s. * - * @author Kyeonghoon Lee * @since 2.0 */ -@FunctionalInterface public interface ChunkProcessor { void process(StepContribution contribution, Chunk chunk) throws Exception; From 60bdadb49aec7d1d239e91a5cefba3868ccfaf60 Mon Sep 17 00:00:00 2001 From: KyeongHoon Lee Date: Tue, 18 Mar 2025 00:32:09 +0900 Subject: [PATCH 5/5] Add test for MariaDB, Postgres, Sqlite, MySQL Signed-off-by: KyeongHoon Lee --- .../MariaDBPagingQueryProviderTests.java | 18 +++++++++++++++ .../MySqlPagingQueryProviderTests.java | 4 ++-- .../PostgresPagingQueryProviderTests.java | 23 ++++++++++++++++++- .../SqlitePagingQueryProviderTests.java | 23 ++++++++++++++++++- 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProviderTests.java index 7921e6da74..53670e13d8 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProviderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProviderTests.java @@ -16,6 +16,7 @@ package org.springframework.batch.item.database.support; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import org.junit.jupiter.api.Test; @@ -26,6 +27,7 @@ /** * @author Mahmoud Ben Hassine + * @author Kyeonghoon Lee */ class MariaDBPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests { @@ -67,6 +69,22 @@ void testGenerateRemainingPagesQueryWithGroupBy() { assertEquals(sql, s); } + @Test + void testGenerateRemainingPagesQueryWithGroupByWithAlias() { + pagingQueryProvider.setSelectClause("SELECT f.id, f.name, f.age"); + pagingQueryProvider.setFromClause("FROM foo f"); + pagingQueryProvider.setWhereClause("f.bar = 1"); + pagingQueryProvider.setGroupClause("f.id, f.dep"); + Map sortKeys = new LinkedHashMap<>(); + sortKeys.put("f.id", Order.ASCENDING); + pagingQueryProvider.setSortKeys(sortKeys); + + String sql = "SELECT * FROM (SELECT f.id, f.name, f.age FROM foo f WHERE f.bar = 1 GROUP BY f.id, f.dep) AS MAIN_QRY WHERE ((id > ?)) ORDER BY id ASC LIMIT " + + pageSize; + String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); + assertEquals(sql, s); + } + @Test void testFirstPageSqlWithAliases() { Map sorts = new HashMap<>(); 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 index 260443145f..9b4cc6930b 100644 --- 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 @@ -75,12 +75,12 @@ void testGenerateRemainingPagesQueryWithGroupByWithAlias() { pagingQueryProvider.setSelectClause("SELECT f.id, f.name, f.age"); pagingQueryProvider.setFromClause("FROM foo f"); pagingQueryProvider.setWhereClause("f.bar = 1"); - pagingQueryProvider.setGroupClause("dep"); + pagingQueryProvider.setGroupClause("f.id, f.dep"); Map sortKeys = new LinkedHashMap<>(); sortKeys.put("f.id", Order.ASCENDING); pagingQueryProvider.setSortKeys(sortKeys); - String sql = "SELECT * FROM (SELECT f.id, f.name, f.age FROM foo f WHERE f.bar = 1 GROUP BY dep) AS MAIN_QRY WHERE ((id > ?)) ORDER BY id ASC LIMIT " + String sql = "SELECT * FROM (SELECT f.id, f.name, f.age FROM foo f WHERE f.bar = 1 GROUP BY f.id, f.dep) AS MAIN_QRY WHERE ((id > ?)) ORDER BY id ASC LIMIT " + pageSize; String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); assertEquals(sql, s); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/PostgresPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/PostgresPagingQueryProviderTests.java index 6ad8a6544b..cbe5675544 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/PostgresPagingQueryProviderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/PostgresPagingQueryProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2025 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. @@ -18,10 +18,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; +import org.springframework.batch.item.database.Order; + +import java.util.LinkedHashMap; +import java.util.Map; /** * @author Thomas Risberg * @author Michael Minella + * @author Kyeonghoon Lee */ class PostgresPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests { @@ -63,6 +68,22 @@ void testGenerateRemainingPagesQueryWithGroupBy() { assertEquals(sql, s); } + @Test + void testGenerateRemainingPagesQueryWithGroupByWithAlias() { + pagingQueryProvider.setSelectClause("SELECT f.id, f.name, f.age"); + pagingQueryProvider.setFromClause("FROM foo f"); + pagingQueryProvider.setWhereClause("f.bar = 1"); + pagingQueryProvider.setGroupClause("f.id, f.dep"); + Map sortKeys = new LinkedHashMap<>(); + sortKeys.put("f.id", Order.ASCENDING); + pagingQueryProvider.setSortKeys(sortKeys); + + String sql = "SELECT * FROM (SELECT f.id, f.name, f.age FROM foo f WHERE f.bar = 1 GROUP BY f.id, f.dep) AS MAIN_QRY WHERE ((id > ?)) ORDER BY id ASC LIMIT " + + pageSize; + String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); + assertEquals(sql, s); + } + @Override String getFirstPageSqlWithMultipleSortKeys() { return "SELECT id, name, age FROM foo WHERE bar = 1 ORDER BY name ASC, id DESC LIMIT 100"; diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlitePagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlitePagingQueryProviderTests.java index 16cf9c3148..425c3ca121 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlitePagingQueryProviderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlitePagingQueryProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2022 the original author or authors. + * Copyright 2014-2025 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. @@ -18,11 +18,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; +import org.springframework.batch.item.database.Order; + +import java.util.LinkedHashMap; +import java.util.Map; /** * @author Thomas Risberg * @author Michael Minella * @author Luke Taylor + * @author Kyeonghoon Lee */ class SqlitePagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests { @@ -64,6 +69,22 @@ void testGenerateRemainingPagesQueryWithGroupBy() { assertEquals(sql, s); } + @Test + void testGenerateRemainingPagesQueryWithGroupByWithAlias() { + pagingQueryProvider.setSelectClause("SELECT f.id, f.name, f.age"); + pagingQueryProvider.setFromClause("FROM foo f"); + pagingQueryProvider.setWhereClause("f.bar = 1"); + pagingQueryProvider.setGroupClause("dep"); + Map sortKeys = new LinkedHashMap<>(); + sortKeys.put("f.id", Order.ASCENDING); + pagingQueryProvider.setSortKeys(sortKeys); + + String sql = "SELECT * FROM (SELECT f.id, f.name, f.age FROM foo f WHERE f.bar = 1 GROUP BY dep) AS MAIN_QRY WHERE ((id > ?)) ORDER BY id ASC LIMIT " + + pageSize; + String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); + assertEquals(sql, s); + } + @Override String getFirstPageSqlWithMultipleSortKeys() { return "SELECT id, name, age FROM foo WHERE bar = 1 ORDER BY name ASC, id DESC LIMIT 100";