From 528dfe627eae11add14e1520903976746c42300f Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Sun, 13 Oct 2024 16:10:27 -0400 Subject: [PATCH 1/9] Refactor Spring Batch support for more flexibility The prior support was limited to limit and offset queries. --- .../dynamic/sql/render/RenderingContext.java | 12 ++++ .../dynamic/sql/render/RenderingStrategy.java | 12 ++++ .../render/FetchFirstPagingModelRenderer.java | 24 ++++---- .../LimitAndOffsetPagingModelRenderer.java | 18 +++--- .../SpringBatchCursorReaderSelectModel.java | 32 ---------- ...tchPagingItemReaderRenderingStrategy.java} | 27 ++++++--- .../SpringBatchPagingReaderSelectModel.java | 59 ------------------- .../SpringBatchProviderAdapter.java | 6 +- .../util/springbatch/SpringBatchUtility.java | 49 +++++++-------- .../CursorReaderBatchConfiguration.java | 6 +- .../PagingReaderBatchConfiguration.java | 7 ++- 11 files changed, 94 insertions(+), 158 deletions(-) delete mode 100644 src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchCursorReaderSelectModel.java rename src/main/java/org/mybatis/dynamic/sql/util/springbatch/{SpringBatchReaderRenderingStrategy.java => SpringBatchPagingItemReaderRenderingStrategy.java} (54%) delete mode 100644 src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingReaderSelectModel.java diff --git a/src/main/java/org/mybatis/dynamic/sql/render/RenderingContext.java b/src/main/java/org/mybatis/dynamic/sql/render/RenderingContext.java index bc953f847..6eb15615c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/RenderingContext.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/RenderingContext.java @@ -66,6 +66,18 @@ private String renderedPlaceHolder(String mapKey, BindableColumn column) .getFormattedJdbcPlaceholder(column, calculatedParameterName, mapKey); } + public RenderedParameterInfo calculateLimitParameterInfo() { + String mapKey = renderingStrategy.formatParameterMapKeyForLimit(sequence); + return new RenderedParameterInfo(mapKey, + renderingStrategy.getFormattedJdbcPlaceholderForLimitOrOffset(calculatedParameterName, mapKey)); + } + + public RenderedParameterInfo calculateOffsetParameterInfo() { + String mapKey = renderingStrategy.formatParameterMapKeyForOffset(sequence); + return new RenderedParameterInfo(mapKey, + renderingStrategy.getFormattedJdbcPlaceholderForLimitOrOffset(calculatedParameterName, mapKey)); + } + public RenderedParameterInfo calculateParameterInfo() { String mapKey = nextMapKey(); return new RenderedParameterInfo(mapKey, renderedPlaceHolder(mapKey)); diff --git a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java index 70b369c73..5924f356c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java @@ -40,6 +40,14 @@ public String formatParameterMapKey(AtomicInteger sequence) { return "p" + sequence.getAndIncrement(); //$NON-NLS-1$ } + public String formatParameterMapKeyForLimit(AtomicInteger sequence) { + return formatParameterMapKey(sequence); + } + + public String formatParameterMapKeyForOffset(AtomicInteger sequence) { + return formatParameterMapKey(sequence); + } + /** * This method generates a binding for a parameter to a placeholder in a generated SQL statement. * @@ -78,6 +86,10 @@ public String formatParameterMapKey(AtomicInteger sequence) { */ public abstract String getFormattedJdbcPlaceholder(String prefix, String parameterName); + public String getFormattedJdbcPlaceholderForLimitOrOffset(String prefix, String parameterName) { + return getFormattedJdbcPlaceholder(prefix, parameterName); + } + /** * This method generates a binding for a parameter to a placeholder in a row based insert statement. * diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/FetchFirstPagingModelRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/FetchFirstPagingModelRenderer.java index fcd62264b..82b61cdf7 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/FetchFirstPagingModelRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/FetchFirstPagingModelRenderer.java @@ -51,30 +51,30 @@ private FragmentAndParameters renderFetchFirstRowsOnly() { } private FragmentAndParameters renderFetchFirstRowsOnly(Long fetchFirstRows) { - RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(); + RenderedParameterInfo limitParameterInfo = renderingContext.calculateLimitParameterInfo(); return FragmentAndParameters - .withFragment("fetch first " + parameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + .withFragment("fetch first " + limitParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + " rows only") //$NON-NLS-1$ - .withParameter(parameterInfo.parameterMapKey(), fetchFirstRows) + .withParameter(limitParameterInfo.parameterMapKey(), fetchFirstRows) .build(); } private FragmentAndParameters renderOffsetOnly(Long offset) { - RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(); - return FragmentAndParameters.withFragment("offset " + parameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + RenderedParameterInfo offsetParameterInfo = renderingContext.calculateOffsetParameterInfo(); + return FragmentAndParameters.withFragment("offset " + offsetParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + " rows") //$NON-NLS-1$ - .withParameter(parameterInfo.parameterMapKey(), offset) + .withParameter(offsetParameterInfo.parameterMapKey(), offset) .build(); } private FragmentAndParameters renderOffsetAndFetchFirstRows(Long offset, Long fetchFirstRows) { - RenderedParameterInfo parameterInfo1 = renderingContext.calculateParameterInfo(); - RenderedParameterInfo parameterInfo2 = renderingContext.calculateParameterInfo(); - return FragmentAndParameters.withFragment("offset " + parameterInfo1.renderedPlaceHolder() //$NON-NLS-1$ - + " rows fetch first " + parameterInfo2.renderedPlaceHolder() //$NON-NLS-1$ + RenderedParameterInfo offsetParameterInfo = renderingContext.calculateOffsetParameterInfo(); + RenderedParameterInfo limitParameterInfo = renderingContext.calculateParameterInfo(); + return FragmentAndParameters.withFragment("offset " + offsetParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + + " rows fetch first " + limitParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + " rows only") //$NON-NLS-1$ - .withParameter(parameterInfo1.parameterMapKey(), offset) - .withParameter(parameterInfo2.parameterMapKey(), fetchFirstRows) + .withParameter(offsetParameterInfo.parameterMapKey(), offset) + .withParameter(limitParameterInfo.parameterMapKey(), fetchFirstRows) .build(); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/LimitAndOffsetPagingModelRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/LimitAndOffsetPagingModelRenderer.java index 008c5a1af..609f4816a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/LimitAndOffsetPagingModelRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/LimitAndOffsetPagingModelRenderer.java @@ -40,19 +40,19 @@ public FragmentAndParameters render() { } private FragmentAndParameters renderLimitOnly() { - RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(); - return FragmentAndParameters.withFragment("limit " + parameterInfo.renderedPlaceHolder()) //$NON-NLS-1$ - .withParameter(parameterInfo.parameterMapKey(), limit) + RenderedParameterInfo limitParameterInfo = renderingContext.calculateLimitParameterInfo(); + return FragmentAndParameters.withFragment("limit " + limitParameterInfo.renderedPlaceHolder()) //$NON-NLS-1$ + .withParameter(limitParameterInfo.parameterMapKey(), limit) .build(); } private FragmentAndParameters renderLimitAndOffset(Long offset) { - RenderedParameterInfo parameterInfo1 = renderingContext.calculateParameterInfo(); - RenderedParameterInfo parameterInfo2 = renderingContext.calculateParameterInfo(); - return FragmentAndParameters.withFragment("limit " + parameterInfo1.renderedPlaceHolder() //$NON-NLS-1$ - + " offset " + parameterInfo2.renderedPlaceHolder()) //$NON-NLS-1$ - .withParameter(parameterInfo1.parameterMapKey(), limit) - .withParameter(parameterInfo2.parameterMapKey(), offset) + RenderedParameterInfo limitParameterInfo = renderingContext.calculateLimitParameterInfo(); + RenderedParameterInfo offsetParameterInfo = renderingContext.calculateOffsetParameterInfo(); + return FragmentAndParameters.withFragment("limit " + limitParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + + " offset " + offsetParameterInfo.renderedPlaceHolder()) //$NON-NLS-1$ + .withParameter(limitParameterInfo.parameterMapKey(), limit) + .withParameter(offsetParameterInfo.parameterMapKey(), offset) .build(); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchCursorReaderSelectModel.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchCursorReaderSelectModel.java deleted file mode 100644 index 5486b31de..000000000 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchCursorReaderSelectModel.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2016-2024 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 - * - * https://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.mybatis.dynamic.sql.util.springbatch; - -import org.mybatis.dynamic.sql.select.SelectModel; -import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; - -public class SpringBatchCursorReaderSelectModel { - - private final SelectModel selectModel; - - public SpringBatchCursorReaderSelectModel(SelectModel selectModel) { - this.selectModel = selectModel; - } - - public SelectStatementProvider render() { - return selectModel.render(SpringBatchUtility.SPRING_BATCH_READER_RENDERING_STRATEGY); - } -} diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchReaderRenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java similarity index 54% rename from src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchReaderRenderingStrategy.java rename to src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java index 48b1b9b8c..566a78bcd 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchReaderRenderingStrategy.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java @@ -15,20 +15,31 @@ */ package org.mybatis.dynamic.sql.util.springbatch; -import org.mybatis.dynamic.sql.BindableColumn; +import java.util.concurrent.atomic.AtomicInteger; + import org.mybatis.dynamic.sql.render.MyBatis3RenderingStrategy; /** - * This rendering strategy should be used for MyBatis3 statements using one of the - * Spring batch readers supplied by mybatis-spring integration (http://www.mybatis.org/spring/). - * Those readers are MyBatisPagingItemReader and MyBatisCursorItemReader. + * This rendering strategy should be used for MyBatis3 statements using the + * MyBatisPagingItemReader supplied by mybatis-spring integration (http://www.mybatis.org/spring/). * */ -public class SpringBatchReaderRenderingStrategy extends MyBatis3RenderingStrategy { +public class SpringBatchPagingItemReaderRenderingStrategy extends MyBatis3RenderingStrategy { + + @Override + public String getFormattedJdbcPlaceholderForLimitOrOffset(String prefix, String parameterName) { + return "#{" //$NON-NLS-1$ + + parameterName + + "}"; //$NON-NLS-1$ + } + + @Override + public String formatParameterMapKeyForLimit(AtomicInteger sequence) { + return "_pagesize"; //$NON-NLS-1$ + } @Override - public String getFormattedJdbcPlaceholder(BindableColumn column, String prefix, String parameterName) { - String newPrefix = SpringBatchUtility.PARAMETER_KEY + "." + prefix; //$NON-NLS-1$ - return super.getFormattedJdbcPlaceholder(column, newPrefix, parameterName); + public String formatParameterMapKeyForOffset(AtomicInteger sequence) { + return "_skiprows"; //$NON-NLS-1$ } } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingReaderSelectModel.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingReaderSelectModel.java deleted file mode 100644 index 648d71218..000000000 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingReaderSelectModel.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2016-2024 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 - * - * https://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.mybatis.dynamic.sql.util.springbatch; - -import java.util.HashMap; -import java.util.Map; - -import org.mybatis.dynamic.sql.select.SelectModel; -import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; - -public class SpringBatchPagingReaderSelectModel { - - private final SelectModel selectModel; - - public SpringBatchPagingReaderSelectModel(SelectModel selectModel) { - this.selectModel = selectModel; - } - - public SelectStatementProvider render() { - SelectStatementProvider selectStatement = - selectModel.render(SpringBatchUtility.SPRING_BATCH_READER_RENDERING_STRATEGY); - return new LimitAndOffsetDecorator(selectStatement); - } - - public static class LimitAndOffsetDecorator implements SelectStatementProvider { - private final Map parameters = new HashMap<>(); - private final String selectStatement; - - public LimitAndOffsetDecorator(SelectStatementProvider delegate) { - parameters.putAll(delegate.getParameters()); - - selectStatement = delegate.getSelectStatement() - + " LIMIT #{_pagesize} OFFSET #{_skiprows}"; //$NON-NLS-1$ - } - - @Override - public Map getParameters() { - return parameters; - } - - @Override - public String getSelectStatement() { - return selectStatement; - } - } -} diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchProviderAdapter.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchProviderAdapter.java index f212a0d04..ce75697cd 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchProviderAdapter.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchProviderAdapter.java @@ -17,13 +17,9 @@ import java.util.Map; -import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; - public class SpringBatchProviderAdapter { public String select(Map parameterValues) { - SelectStatementProvider selectStatement = - (SelectStatementProvider) parameterValues.get(SpringBatchUtility.PARAMETER_KEY); - return selectStatement.getSelectStatement(); + return (String) parameterValues.get(SpringBatchUtility.PARAMETER_KEY); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchUtility.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchUtility.java index 63b64429a..1155da806 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchUtility.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchUtility.java @@ -18,48 +18,39 @@ import java.util.HashMap; import java.util.Map; -import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.render.RenderingStrategy; -import org.mybatis.dynamic.sql.select.QueryExpressionDSL; -import org.mybatis.dynamic.sql.select.SelectDSL; import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; public class SpringBatchUtility { private SpringBatchUtility() {} - public static final String PARAMETER_KEY = "mybatis3_dsql_query"; //$NON-NLS-1$ - - public static final RenderingStrategy SPRING_BATCH_READER_RENDERING_STRATEGY = - new SpringBatchReaderRenderingStrategy(); - - public static Map toParameterValues(SelectStatementProvider selectStatement) { - Map parameterValues = new HashMap<>(); - parameterValues.put(PARAMETER_KEY, selectStatement); - return parameterValues; - } + static final String PARAMETER_KEY = "mybatis3_dsql_query"; //$NON-NLS-1$ /** - * Select builder that renders in a manner appropriate for the MyBatisPagingItemReader. - * - *

Important rendered SQL will contain LIMIT and OFFSET clauses in the SELECT statement. If your database - * (Oracle) does not support LIMIT and OFFSET, the queries will fail. + * Constant for use in a query intended for use with the MyBatisPagingItemReader. + * This value will not be used in the query at runtime because MyBatis Spring integration + * will supply a value for _skiprows. * - * @param selectList a column list for the SELECT statement - * @return FromGatherer used to continue a SELECT statement + *

See https://mybatis.org/spring/batch.html for details. */ - public static QueryExpressionDSL.FromGatherer selectForPaging( - BasicColumn... selectList) { - return SelectDSL.select(SpringBatchPagingReaderSelectModel::new, selectList); - } + public static final long MYBATIS_SPRING_BATCH_SKIPROWS = -437L; /** - * Select builder that renders in a manner appropriate for the MyBatisCursorItemReader. + * Constant for use in a query intended for use with the MyBatisPagingItemReader. + * This value will not be used in the query at runtime because MyBatis Spring integration + * will supply a value for _pagesize. * - * @param selectList a column list for the SELECT statement - * @return FromGatherer used to continue a SELECT statement + *

See https://mybatis.org/spring/batch.html for details. */ - public static QueryExpressionDSL.FromGatherer selectForCursor( - BasicColumn... selectList) { - return SelectDSL.select(SpringBatchCursorReaderSelectModel::new, selectList); + public static final long MYBATIS_SPRING_BATCH_PAGESIZE = -439L; + + public static final RenderingStrategy SPRING_BATCH_PAGING_ITEM_READER_RENDERING_STRATEGY = + new SpringBatchPagingItemReaderRenderingStrategy(); + + public static Map toParameterValues(SelectStatementProvider selectStatement) { + Map parameterValues = new HashMap<>(); + parameterValues.put(PARAMETER_KEY, selectStatement.getSelectStatement()); + parameterValues.put("parameters", selectStatement.getParameters()); //$NON-NLS-1$ + return parameterValues; } } diff --git a/src/test/java/examples/springbatch/cursor/CursorReaderBatchConfiguration.java b/src/test/java/examples/springbatch/cursor/CursorReaderBatchConfiguration.java index 823f1c631..838fe9d41 100644 --- a/src/test/java/examples/springbatch/cursor/CursorReaderBatchConfiguration.java +++ b/src/test/java/examples/springbatch/cursor/CursorReaderBatchConfiguration.java @@ -18,10 +18,12 @@ import static examples.springbatch.mapper.PersonDynamicSqlSupport.lastName; import static examples.springbatch.mapper.PersonDynamicSqlSupport.person; import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo; +import static org.mybatis.dynamic.sql.SqlBuilder.select; import javax.sql.DataSource; import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.dynamic.sql.render.RenderingStrategies; import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider; import org.mybatis.dynamic.sql.util.springbatch.SpringBatchUtility; @@ -89,11 +91,11 @@ public PlatformTransactionManager transactionManager(DataSource dataSource) { @Bean public MyBatisCursorItemReader reader(SqlSessionFactory sqlSessionFactory) { - SelectStatementProvider selectStatement = SpringBatchUtility.selectForCursor(person.allColumns()) + SelectStatementProvider selectStatement = select(person.allColumns()) .from(person) .where(lastName, isEqualTo("flintstone")) .build() - .render(); + .render(RenderingStrategies.MYBATIS3); MyBatisCursorItemReader reader = new MyBatisCursorItemReader<>(); reader.setQueryId(PersonMapper.class.getName() + ".selectMany"); diff --git a/src/test/java/examples/springbatch/paging/PagingReaderBatchConfiguration.java b/src/test/java/examples/springbatch/paging/PagingReaderBatchConfiguration.java index 41353ab35..2fa74c862 100644 --- a/src/test/java/examples/springbatch/paging/PagingReaderBatchConfiguration.java +++ b/src/test/java/examples/springbatch/paging/PagingReaderBatchConfiguration.java @@ -17,6 +17,7 @@ import static examples.springbatch.mapper.PersonDynamicSqlSupport.*; import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo; +import static org.mybatis.dynamic.sql.SqlBuilder.select; import javax.sql.DataSource; @@ -88,12 +89,14 @@ public PlatformTransactionManager transactionManager(DataSource dataSource) { @Bean public MyBatisPagingItemReader reader(SqlSessionFactory sqlSessionFactory) { - SelectStatementProvider selectStatement = SpringBatchUtility.selectForPaging(person.allColumns()) + SelectStatementProvider selectStatement = select(person.allColumns()) .from(person) .where(forPagingTest, isEqualTo(true)) .orderBy(id) + .limit(SpringBatchUtility.MYBATIS_SPRING_BATCH_PAGESIZE) + .offset(SpringBatchUtility.MYBATIS_SPRING_BATCH_SKIPROWS) .build() - .render(); + .render(SpringBatchUtility.SPRING_BATCH_PAGING_ITEM_READER_RENDERING_STRATEGY); MyBatisPagingItemReader reader = new MyBatisPagingItemReader<>(); reader.setQueryId(PersonMapper.class.getName() + ".selectMany"); From 8214313b936c759f2b009c2dfaed6e623bd011ff Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Sun, 13 Oct 2024 16:26:49 -0400 Subject: [PATCH 2/9] Fix documentation link --- .../SpringBatchPagingItemReaderRenderingStrategy.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java index 566a78bcd..d45d56c0e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java @@ -21,8 +21,8 @@ /** * This rendering strategy should be used for MyBatis3 statements using the - * MyBatisPagingItemReader supplied by mybatis-spring integration (http://www.mybatis.org/spring/). - * + * MyBatisPagingItemReader supplied by mybatis-spring integration + * (http://www.mybatis.org/spring/). */ public class SpringBatchPagingItemReaderRenderingStrategy extends MyBatis3RenderingStrategy { From e41a1129e66a78e692204436d86d538847628ffa Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Tue, 15 Oct 2024 14:38:12 -0400 Subject: [PATCH 3/9] Improve Javadocs --- .../sql/util/springbatch/SpringBatchUtility.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchUtility.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchUtility.java index 1155da806..01cae8964 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchUtility.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchUtility.java @@ -31,6 +31,9 @@ private SpringBatchUtility() {} * This value will not be used in the query at runtime because MyBatis Spring integration * will supply a value for _skiprows. * + *

This value can be used as a parameter for the "offset" method in a query to make the intention + * clear that the actual runtime value will be supplied by MyBatis Spring integration. + * *

See https://mybatis.org/spring/batch.html for details. */ public static final long MYBATIS_SPRING_BATCH_SKIPROWS = -437L; @@ -40,6 +43,9 @@ private SpringBatchUtility() {} * This value will not be used in the query at runtime because MyBatis Spring integration * will supply a value for _pagesize. * + *

This value can be used as a parameter for the "limit" or "fetchFirst" method in a query to make the intention + * clear that the actual runtime value will be supplied by MyBatis Spring integration. + * *

See https://mybatis.org/spring/batch.html for details. */ public static final long MYBATIS_SPRING_BATCH_PAGESIZE = -439L; @@ -48,9 +54,9 @@ private SpringBatchUtility() {} new SpringBatchPagingItemReaderRenderingStrategy(); public static Map toParameterValues(SelectStatementProvider selectStatement) { - Map parameterValues = new HashMap<>(); + var parameterValues = new HashMap(); parameterValues.put(PARAMETER_KEY, selectStatement.getSelectStatement()); - parameterValues.put("parameters", selectStatement.getParameters()); //$NON-NLS-1$ + parameterValues.put(RenderingStrategy.DEFAULT_PARAMETER_PREFIX, selectStatement.getParameters()); return parameterValues; } } From e262018aecbca866561f7c78faf9dc9f07ed328a Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 16 Oct 2024 13:33:55 -0400 Subject: [PATCH 4/9] Improve Javadocs --- .../dynamic/sql/render/RenderingStrategy.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java index 5924f356c..afc588a7a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java @@ -36,14 +36,38 @@ public abstract class RenderingStrategy { public static final String DEFAULT_PARAMETER_PREFIX = "parameters"; //$NON-NLS-1$ + /** + * Return a unique key that can be used to place a parameter value in the parameter map + * + * @param sequence a sequence for calculating a unique value + * @return a key used to place the parameter value in the parameter map + */ public String formatParameterMapKey(AtomicInteger sequence) { return "p" + sequence.getAndIncrement(); //$NON-NLS-1$ } + /** + * Return a parameter map key intended as a parameter for a limit or fetch first query. + * + *

By default, this parameter is treated the same as any other. This method is a hook to support + * MyBatis Spring Batch. + * + * @param sequence a sequence for calculating a unique value + * @return a key used to place the parameter value in the parameter map + */ public String formatParameterMapKeyForLimit(AtomicInteger sequence) { return formatParameterMapKey(sequence); } + /** + * Return a parameter map key intended as a parameter for a query offset. + * + *

By default, this parameter is treated the same as any other. This method is a hook to support + * MyBatis Spring Batch. + * + * @param sequence a sequence for calculating a unique value + * @return a key used to place the parameter value in the parameter map + */ public String formatParameterMapKeyForOffset(AtomicInteger sequence) { return formatParameterMapKey(sequence); } @@ -86,6 +110,19 @@ public String formatParameterMapKeyForOffset(AtomicInteger sequence) { */ public abstract String getFormattedJdbcPlaceholder(String prefix, String parameterName); + /** + * This method generates a binding for a parameter to a placeholder in a generated SQL statement. + * + *

This method is used to generate bindings for limit, offset, and fetch first parameters. By default, these + * parameters are treated the same as any other. This method supports MyBatis Spring Batch integration where the + * parameter keys have predefined values and need special handling. + * + * @param prefix parameter prefix used for locating the parameters in a SQL provider object. Typically, will be + * {@link RenderingStrategy#DEFAULT_PARAMETER_PREFIX}. This is ignored for Spring. + * @param parameterName name of the parameter. Typically generated by calling + * {@link RenderingStrategy#formatParameterMapKey(AtomicInteger)} + * @return the generated binding + */ public String getFormattedJdbcPlaceholderForLimitOrOffset(String prefix, String parameterName) { return getFormattedJdbcPlaceholder(prefix, parameterName); } From 4e8bf745f2fe257537b60305e304975a52889fcd Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 16 Oct 2024 13:34:18 -0400 Subject: [PATCH 5/9] Use new limit method for delete and update statements --- .../org/mybatis/dynamic/sql/delete/render/DeleteRenderer.java | 2 +- .../org/mybatis/dynamic/sql/update/render/UpdateRenderer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteRenderer.java b/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteRenderer.java index 8a9c945d4..fa4cd0d28 100644 --- a/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteRenderer.java @@ -84,7 +84,7 @@ private Optional calculateLimitClause() { } private FragmentAndParameters renderLimitClause(Long limit) { - RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(); + RenderedParameterInfo parameterInfo = renderingContext.calculateLimitParameterInfo(); return FragmentAndParameters.withFragment("limit " + parameterInfo.renderedPlaceHolder()) //$NON-NLS-1$ .withParameter(parameterInfo.parameterMapKey(), limit) diff --git a/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateRenderer.java b/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateRenderer.java index f11662c02..352c1de7a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateRenderer.java @@ -109,7 +109,7 @@ private Optional calculateLimitClause() { } private FragmentAndParameters renderLimitClause(Long limit) { - RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(); + RenderedParameterInfo parameterInfo = renderingContext.calculateLimitParameterInfo(); return FragmentAndParameters.withFragment("limit " + parameterInfo.renderedPlaceHolder()) //$NON-NLS-1$ .withParameter(parameterInfo.parameterMapKey(), limit) From 3ade4fe06c7626799a10cf97ae7b42ed5c53f819 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 23 Oct 2024 16:37:32 -0400 Subject: [PATCH 6/9] Documentation --- CHANGELOG.md | 4 + src/site/markdown/docs/springBatch.md | 180 ++++++++++++++++---------- 2 files changed, 116 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d46635fb..9a61b8b89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ worked to make these changes as minimal as possible. **Potentially Breaking Changes:** +- If you use this library with MyBatis' Spring Batch integration, you will need to make changes as we have + refactored that support to be more flexible. Please see the + [Spring Batch](https://mybatis.org/mybatis-dynamic-sql/docs/springBatch.html) documentation page to see the new usage + details. - If you have created any custom implementations of `SortSpecification`, you will need to update those implementations due to a new rendering strategy for ORDER BY phrases. The old methods `isDescending` and `orderByName` are removed in favor of a new method `renderForOrderBy` diff --git a/src/site/markdown/docs/springBatch.md b/src/site/markdown/docs/springBatch.md index 4b1855a97..1c5bfda66 100644 --- a/src/site/markdown/docs/springBatch.md +++ b/src/site/markdown/docs/springBatch.md @@ -1,100 +1,144 @@ # Spring Batch Support This library provides some utilities to make it easier to interact with the MyBatis Spring Batch support. -## The Problem +MyBatis Spring provides support for interacting with Spring Batch (see +[http://www.mybatis.org/spring/batch.html](http://www.mybatis.org/spring/batch.html)). This support consists of +specialized implementations of Spring Batch's `ItemReader` and `ItemWriter` interfaces that have support for MyBatis +mappers. -MyBatis Spring support provides utility classes for interacting with Spring Batch (see [http://www.mybatis.org/spring/batch.html](http://www.mybatis.org/spring/batch.html)). These classes are specialized implementations of Spring Batch's `ItemReader` and `ItemWriter` interfaces that have support for MyBatis mappers. +The `ItemWriter` implementation works with SQL generated by MyBatis Dynamic SQL with no modification needed. -The `ItemWriter` implementations work with SQL generated by MyBatis Dynamic SQL with no modification needed. +The `ItemReader` implementations need special care. Those classes assume that all query parameters will be placed in a +Map (as per usual when using multiple parameters in a query). MyBatis Dynamic SQL, by default, builds a parameter +object that is intended to be the only parameter for a query. The library contains utilities for overcoming this +difficulty. -The `ItemReader` implementations need special care. Those classes assume that all query parameters will be placed in a Map (as per usual when using multiple parameters in a query). MyBatis Dynamic SQL, by default, builds a parameter object that should be the only parameter in a query and will not work when placed in a Map of parameters. +## Using MyBatisCursorItemReader -## The Solution +The `MyBatisCursorItemReader` class works with built-in support for cursor based queries in MyBatis. Queries of this +type will read row by row and MyBatis will convert each result row to a result object without having to read the entire +result set into memory. The normal rendering for MyBatis will work for queries using this reader, but special care +must be taken to prepare the parameter values for use with this reader. See the following example: -The solution involves these steps: - -1. The SQL must be rendered such that the parameter markers are aware of the enclosing parameter Map in the `ItemReader` -1. The `SelectStatementProvider` must be placed in the `ItemReader` parameter Map with a known key. -1. The `@SelectProvider` must be configured to be aware of the enclosing parameter Map - -MyBatis Dynamic SQL provides utilities for each of these requirements. Each utility uses a shared Map key for consistency. - -## Spring Batch Item Readers - -MyBatis Spring support supplies two implementations of the `ItemReader` interface: - -1. `org.mybatis.spring.batch.MyBatisCursorItemReader` - for queries that can be efficiently processed through a single select statement and a cursor -1. `org.mybatis.spring.batch.MyBatisPagingItemReader` - for queries that should be processed as a series of paged selects. Note that MyBatis does not provide any native support for paged queries - it is up to the user to write SQL for paging. The `MyBatisPagingItemWriter` simply makes properties available that specify which page should be read currently. - -MyBatis Dynamic SQL supplies specialized select statements that will render properly for the different implementations of `ItemReader`: - -1. `SpringBatchUtility.selectForCursor(...)` will create a select statement that is appropriate for the `MyBatisCursorItemReader` - a single select statement that will be read with a cursor -1. `SpringBatchUtility.selectForPaging(...)` will create a select statement that is appropriate for the `MyBatisPagingItemReader` - a select statement that will be called multiple times - one for each page as configured on the batch job. - -**Very Important:** The paging implementation will only work for databases that support limit and offset in select statements. Fortunately, most databases do support this - with the notable exception of Oracle. - - -### Rendering for Cursor +```java +@Bean +public MyBatisCursorItemReader reader(SqlSessionFactory sqlSessionFactory) { + SelectStatementProvider selectStatement = select(person.allColumns()) + .from(person) + .where(lastName, isEqualTo("flintstone")) + .build() + .render(RenderingStrategies.MYBATIS3); + + MyBatisCursorItemReader reader = new MyBatisCursorItemReader<>(); + reader.setQueryId(PersonMapper.class.getName() + ".selectMany"); + reader.setSqlSessionFactory(sqlSessionFactory); + reader.setParameterValues(SpringBatchUtility.toParameterValues(selectStatement)); + return reader; +} +``` -Queries intended for the `MyBatisCursorItemReader` should be rendered as follows: +Note the use of `SpringBatchUtility.toParameterValues(...)`. This utility will set up the parameter Map correctly for the +rendered statement, and for use with a library supplied `@selectProvider`. See the following for an example of the mapper +method used for the query coded above: ```java - SelectStatementProvider selectStatement = SpringBatchUtility.selectForCursor(person.allColumns()) - .from(person) - .where(lastName, isEqualTo("flintstone")) - .build() - .render(); // renders for MyBatisCursorItemReader +@Mapper +public interface PersonMapper { + + @SelectProvider(type=SpringBatchProviderAdapter.class, method="select") + @Results({ + @Result(column="id", property="id", id=true), + @Result(column="first_name", property="firstName"), + @Result(column="last_name", property="lastName") + }) + List selectMany(Map parameterValues); +} ``` -### Rendering for Paging +Note the use of the `SpringBatchProviderAdapter` - that adapter knows how to retrieve the rendered queries from the +parameter map initialed in the method above. -Queries intended for the `MyBatisPagingItemReader` should be rendered as follows: +### Migrating from 1.x Support for MyBatisCursorItemReader + +In version 1.x, the library supplied a special utility for creating a select statement as follows: ```java - SelectStatementProvider selectStatement = SpringBatchUtility.selectForPaging(person.allColumns()) - .from(person) - .where(lastName, isEqualTo("flintstone")) - .build() - .render(); // renders for MyBatisPagingItemReader +SelectStatementProvider selectStatement = SpringBatchUtility.selectForCursor(person.allColumns()) + .from(person) + .where(lastName, isEqualTo("flintstone")) + .build() + .render(); ``` -## Creating the Parameter Map +That utility method was limited in capability. The new method allows the full capabilities of the library. To migrate, +follow these steps: -The `SpringBatchUtility` provides a method to create the parameter values Map needed by the MyBatis Spring `ItemReader` implementations. It can be used as follows: +1. Replace `SpringBatchUtility.selectForCursor(...)` with `SqlBuilder.select(...)` +2. Replace `render()` with `render(RenderingStrategies.MYBATIS3)` -For cursor based queries... +## Using MyBatisPagingItemReader -```java - MyBatisCursorItemReader reader = new MyBatisCursorItemReader<>(); - reader.setQueryId(PersonMapper.class.getName() + ".selectMany"); - reader.setSqlSessionFactory(sqlSessionFactory); - reader.setParameterValues(SpringBatchUtility.toParameterValues(selectStatement)); // create parameter map -``` -For paging based queries... +The `MyBatisPagingItemReader` class works with paging queries - queries that read rows in pages and process page by page +rather than row by row. The normal rendering for MyBatis will work NOT for queries using this reader because MyBatis +Spring support supplies specially named parameters for page size, offset, etc. So the query must be rendered properly +to respond to these parameter values that are supplied at runtime. As with the other reader, special care +must also be taken to prepare the parameter values for use with this reader. See the following example: ```java - MyBatisPagingItemReader reader = new MyBatisPagingItemReader<>(); - reader.setQueryId(PersonMapper.class.getName() + ".selectMany"); - reader.setSqlSessionFactory(sqlSessionFactory); - reader.setPageSize(7); - reader.setParameterValues(SpringBatchUtility.toParameterValues(selectStatement)); // create parameter map +@Bean +public MyBatisPagingItemReader reader(SqlSessionFactory sqlSessionFactory) { + SelectStatementProvider selectStatement = select(person.allColumns()) + .from(person) + .where(forPagingTest, isEqualTo(true)) + .orderBy(id) + .limit(SpringBatchUtility.MYBATIS_SPRING_BATCH_PAGESIZE) + .offset(SpringBatchUtility.MYBATIS_SPRING_BATCH_SKIPROWS) + .build() + .render(SpringBatchUtility.SPRING_BATCH_PAGING_ITEM_READER_RENDERING_STRATEGY); + + MyBatisPagingItemReader reader = new MyBatisPagingItemReader<>(); + reader.setQueryId(PersonMapper.class.getName() + ".selectMany"); + reader.setSqlSessionFactory(sqlSessionFactory); + reader.setParameterValues(SpringBatchUtility.toParameterValues(selectStatement)); + reader.setPageSize(7); + return reader; +} ``` +Notice the following important items: +1. The `limit` and `offset` methods in the query are used to set up paging support in the query. With MyBatis Spring + batch support, the integration library will supply values for those parameters at runtime. Any values you code in the + select statement will be ignored - only the values supplied by the library will be used. We supply two constants + to make this clearer: `MYBATIS_SPRING_BATCH_PAGESIZE` and `MYBATIS_SPRING_BATCH_SKIPROWS`. You can use these values + to make the code clearer, but again the values will be ignored at runtime. +2. The query must be rendered with the `SPRING_BATCH_PAGING_ITEM_READER_RENDERING_STRATEGY` rendering strategy. This + rendering strategy will render the query so that it will respond properly to the runtime values supplied for page size + and skip rows. -## Specialized @SelectProvider Adapter +### Migrating from 1.x Support for MyBatisPagingItemReader -MyBatis mapper methods should be configured to use the specialized `@SelectProvider` adapter as follows: +In version 1.x, the library supplied a special utility for creating a select statement as follows: ```java - @SelectProvider(type=SpringBatchProviderAdapter.class, method="select") // use the Spring batch adapter - @Results({ - @Result(column="id", property="id", id=true), - @Result(column="first_name", property="firstName"), - @Result(column="last_name", property="lastName") - }) - List selectMany(Map parameterValues); +SelectStatementProvider selectStatement = SpringBatchUtility.selectForPaging(person.allColumns()) + .from(person) + .where(forPagingTest, isEqualTo(true)) + .orderBy(id) + .build() + .render(); ``` -## Complete Example +That utility method was very limited in capability. It only supported limit and offset based queries - which are not +supported in all databases. The new method allows the full capabilities of the library. To migrate, +follow these steps: + +1. Replace `SpringBatchUtility.selectForPaging(...)` with `SqlBuilder.select(...)` +2. Add `limit()`, `fetchFirst()`, and `offset()` method calls as appropriate for your query and database +3. Replace `render()` with `render(RenderingStrategies.SPRING_BATCH_PAGING_ITEM_READER_RENDERING_STRATEGY)` + + +## Complete Examples -The unit tests for MyBatis Dynamic SQL include a complete example of using MyBatis Spring Batch support using the MyBatis supplied reader as well as both types of MyBatis supplied writers. You can see the full example here: [https://github.com/mybatis/mybatis-dynamic-sql/tree/master/src/test/java/examples/springbatch](https://github.com/mybatis/mybatis-dynamic-sql/tree/master/src/test/java/examples/springbatch) +The unit tests for MyBatis Dynamic SQL include a complete example of using MyBatis Spring Batch support using the +MyBatis supplied reader as well as both types of MyBatis supplied writers. You can see the full example +here: [https://github.com/mybatis/mybatis-dynamic-sql/tree/master/src/test/java/examples/springbatch](https://github.com/mybatis/mybatis-dynamic-sql/tree/master/src/test/java/examples/springbatch) From d4005a757e49d554d95692b73e841bf9f6145ec6 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 23 Oct 2024 17:51:58 -0400 Subject: [PATCH 7/9] Coverage --- pom.xml | 2 + .../dynamic/sql/render/RenderingContext.java | 19 +++--- .../dynamic/sql/render/RenderingStrategy.java | 19 +++++- .../render/FetchFirstPagingModelRenderer.java | 12 ++-- ...atchPagingItemReaderRenderingStrategy.java | 2 +- src/site/markdown/docs/springBatch.md | 2 +- .../examples/simple/MyBatisMapToRowTest.java | 1 - .../springbatch/SpringBatchRenderingTest.java | 65 +++++++++++++++++++ 8 files changed, 99 insertions(+), 23 deletions(-) create mode 100644 src/test/java/examples/springbatch/SpringBatchRenderingTest.java diff --git a/pom.xml b/pom.xml index 991746894..772950c2b 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,8 @@ 17 17 + 17 + 17 5.11.1 5.1.2 diff --git a/src/main/java/org/mybatis/dynamic/sql/render/RenderingContext.java b/src/main/java/org/mybatis/dynamic/sql/render/RenderingContext.java index 6eb15615c..553ecd090 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/RenderingContext.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/RenderingContext.java @@ -57,30 +57,27 @@ private String nextMapKey() { return renderingStrategy.formatParameterMapKey(sequence); } - private String renderedPlaceHolder(String mapKey) { - return renderingStrategy.getFormattedJdbcPlaceholder(calculatedParameterName, mapKey); - } - private String renderedPlaceHolder(String mapKey, BindableColumn column) { return column.renderingStrategy().orElse(renderingStrategy) .getFormattedJdbcPlaceholder(column, calculatedParameterName, mapKey); } + public RenderedParameterInfo calculateFetchFirstRowsParameterInfo() { + String mapKey = renderingStrategy.formatParameterMapKeyForFetchFirstRows(sequence); + return new RenderedParameterInfo(mapKey, + renderingStrategy.getFormattedJdbcPlaceholderForPagingParameters(calculatedParameterName, mapKey)); + } + public RenderedParameterInfo calculateLimitParameterInfo() { String mapKey = renderingStrategy.formatParameterMapKeyForLimit(sequence); return new RenderedParameterInfo(mapKey, - renderingStrategy.getFormattedJdbcPlaceholderForLimitOrOffset(calculatedParameterName, mapKey)); + renderingStrategy.getFormattedJdbcPlaceholderForPagingParameters(calculatedParameterName, mapKey)); } public RenderedParameterInfo calculateOffsetParameterInfo() { String mapKey = renderingStrategy.formatParameterMapKeyForOffset(sequence); return new RenderedParameterInfo(mapKey, - renderingStrategy.getFormattedJdbcPlaceholderForLimitOrOffset(calculatedParameterName, mapKey)); - } - - public RenderedParameterInfo calculateParameterInfo() { - String mapKey = nextMapKey(); - return new RenderedParameterInfo(mapKey, renderedPlaceHolder(mapKey)); + renderingStrategy.getFormattedJdbcPlaceholderForPagingParameters(calculatedParameterName, mapKey)); } public RenderedParameterInfo calculateParameterInfo(BindableColumn column) { diff --git a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java index afc588a7a..969a5e094 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java @@ -37,7 +37,7 @@ public abstract class RenderingStrategy { public static final String DEFAULT_PARAMETER_PREFIX = "parameters"; //$NON-NLS-1$ /** - * Return a unique key that can be used to place a parameter value in the parameter map + * Generate a unique key that can be used to place a parameter value in the parameter map * * @param sequence a sequence for calculating a unique value * @return a key used to place the parameter value in the parameter map @@ -47,7 +47,20 @@ public String formatParameterMapKey(AtomicInteger sequence) { } /** - * Return a parameter map key intended as a parameter for a limit or fetch first query. + * Return a parameter map key intended as a parameter for a fetch first query. + * + *

By default, this parameter is treated the same as any other. This method is a hook to support + * MyBatis Spring Batch. + * + * @param sequence a sequence for calculating a unique value + * @return a key used to place the parameter value in the parameter map + */ + public String formatParameterMapKeyForFetchFirstRows(AtomicInteger sequence) { + return formatParameterMapKeyForLimit(sequence); + } + + /** + * Return a parameter map key intended as a parameter for a limit query. * *

By default, this parameter is treated the same as any other. This method is a hook to support * MyBatis Spring Batch. @@ -123,7 +136,7 @@ public String formatParameterMapKeyForOffset(AtomicInteger sequence) { * {@link RenderingStrategy#formatParameterMapKey(AtomicInteger)} * @return the generated binding */ - public String getFormattedJdbcPlaceholderForLimitOrOffset(String prefix, String parameterName) { + public String getFormattedJdbcPlaceholderForPagingParameters(String prefix, String parameterName) { return getFormattedJdbcPlaceholder(prefix, parameterName); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/FetchFirstPagingModelRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/FetchFirstPagingModelRenderer.java index 82b61cdf7..0ad493620 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/FetchFirstPagingModelRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/FetchFirstPagingModelRenderer.java @@ -51,11 +51,11 @@ private FragmentAndParameters renderFetchFirstRowsOnly() { } private FragmentAndParameters renderFetchFirstRowsOnly(Long fetchFirstRows) { - RenderedParameterInfo limitParameterInfo = renderingContext.calculateLimitParameterInfo(); + RenderedParameterInfo fetchFirstParameterInfo = renderingContext.calculateFetchFirstRowsParameterInfo(); return FragmentAndParameters - .withFragment("fetch first " + limitParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + .withFragment("fetch first " + fetchFirstParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + " rows only") //$NON-NLS-1$ - .withParameter(limitParameterInfo.parameterMapKey(), fetchFirstRows) + .withParameter(fetchFirstParameterInfo.parameterMapKey(), fetchFirstRows) .build(); } @@ -69,12 +69,12 @@ private FragmentAndParameters renderOffsetOnly(Long offset) { private FragmentAndParameters renderOffsetAndFetchFirstRows(Long offset, Long fetchFirstRows) { RenderedParameterInfo offsetParameterInfo = renderingContext.calculateOffsetParameterInfo(); - RenderedParameterInfo limitParameterInfo = renderingContext.calculateParameterInfo(); + RenderedParameterInfo fetchFirstParameterInfo = renderingContext.calculateFetchFirstRowsParameterInfo(); return FragmentAndParameters.withFragment("offset " + offsetParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ - + " rows fetch first " + limitParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + + " rows fetch first " + fetchFirstParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + " rows only") //$NON-NLS-1$ .withParameter(offsetParameterInfo.parameterMapKey(), offset) - .withParameter(limitParameterInfo.parameterMapKey(), fetchFirstRows) + .withParameter(fetchFirstParameterInfo.parameterMapKey(), fetchFirstRows) .build(); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java index d45d56c0e..791e89bfc 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java @@ -27,7 +27,7 @@ public class SpringBatchPagingItemReaderRenderingStrategy extends MyBatis3RenderingStrategy { @Override - public String getFormattedJdbcPlaceholderForLimitOrOffset(String prefix, String parameterName) { + public String getFormattedJdbcPlaceholderForPagingParameters(String prefix, String parameterName) { return "#{" //$NON-NLS-1$ + parameterName + "}"; //$NON-NLS-1$ diff --git a/src/site/markdown/docs/springBatch.md b/src/site/markdown/docs/springBatch.md index 1c5bfda66..11ec90d6d 100644 --- a/src/site/markdown/docs/springBatch.md +++ b/src/site/markdown/docs/springBatch.md @@ -85,7 +85,7 @@ to respond to these parameter values that are supplied at runtime. As with the o must also be taken to prepare the parameter values for use with this reader. See the following example: ```java -@Bean +@Bean public MyBatisPagingItemReader reader(SqlSessionFactory sqlSessionFactory) { SelectStatementProvider selectStatement = select(person.allColumns()) .from(person) diff --git a/src/test/java/examples/simple/MyBatisMapToRowTest.java b/src/test/java/examples/simple/MyBatisMapToRowTest.java index 2b6f714b2..a542d36f6 100644 --- a/src/test/java/examples/simple/MyBatisMapToRowTest.java +++ b/src/test/java/examples/simple/MyBatisMapToRowTest.java @@ -28,7 +28,6 @@ import java.io.InputStreamReader; import java.sql.Connection; import java.sql.DriverManager; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.IntStream; diff --git a/src/test/java/examples/springbatch/SpringBatchRenderingTest.java b/src/test/java/examples/springbatch/SpringBatchRenderingTest.java new file mode 100644 index 000000000..48873b375 --- /dev/null +++ b/src/test/java/examples/springbatch/SpringBatchRenderingTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2016-2024 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 + * + * https://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 examples.springbatch; + +import static examples.springbatch.mapper.PersonDynamicSqlSupport.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mybatis.dynamic.sql.SqlBuilder.isLike; +import static org.mybatis.dynamic.sql.SqlBuilder.select; + +import org.junit.jupiter.api.Test; +import org.mybatis.dynamic.sql.util.springbatch.SpringBatchUtility; + +class SpringBatchRenderingTest { + + @Test + void renderLimit() { + var selectStatement = select(person.allColumns()) + .from(person) + .where(firstName, isLike("%f%")) + .limit(SpringBatchUtility.MYBATIS_SPRING_BATCH_PAGESIZE) + .offset(SpringBatchUtility.MYBATIS_SPRING_BATCH_SKIPROWS) + .build() + .render(SpringBatchUtility.SPRING_BATCH_PAGING_ITEM_READER_RENDERING_STRATEGY); + + assertThat(selectStatement.getSelectStatement()) + .isEqualTo(""" + select * \ + from person \ + where first_name like #{parameters.p1,jdbcType=VARCHAR} \ + limit #{_pagesize} \ + offset #{_skiprows}"""); + } + + @Test + void renderFetchFirst() { + var selectStatement = select(person.allColumns()) + .from(person) + .where(firstName, isLike("%f%")) + .offset(SpringBatchUtility.MYBATIS_SPRING_BATCH_SKIPROWS) + .fetchFirst(SpringBatchUtility.MYBATIS_SPRING_BATCH_PAGESIZE).rowsOnly() + .build() + .render(SpringBatchUtility.SPRING_BATCH_PAGING_ITEM_READER_RENDERING_STRATEGY); + + assertThat(selectStatement.getSelectStatement()) + .isEqualTo(""" + select * \ + from person \ + where first_name like #{parameters.p1,jdbcType=VARCHAR} \ + offset #{_skiprows} rows \ + fetch first #{_pagesize} rows only"""); + } +} From adc2931b74bc9d67c6f25d058b0b8c4a24eb3803 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Thu, 24 Oct 2024 11:08:54 -0400 Subject: [PATCH 8/9] Better pattern for fetch first --- .../org/mybatis/dynamic/sql/render/RenderingStrategy.java | 2 +- .../SpringBatchPagingItemReaderRenderingStrategy.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java index 969a5e094..fb10ea99b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java @@ -56,7 +56,7 @@ public String formatParameterMapKey(AtomicInteger sequence) { * @return a key used to place the parameter value in the parameter map */ public String formatParameterMapKeyForFetchFirstRows(AtomicInteger sequence) { - return formatParameterMapKeyForLimit(sequence); + return formatParameterMapKey(sequence); } /** diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java index 791e89bfc..c87c4a03e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java @@ -33,6 +33,11 @@ public String getFormattedJdbcPlaceholderForPagingParameters(String prefix, Stri + "}"; //$NON-NLS-1$ } + @Override + public String formatParameterMapKeyForFetchFirstRows(AtomicInteger sequence) { + return "_pagesize"; //$NON-NLS-1$ + } + @Override public String formatParameterMapKeyForLimit(AtomicInteger sequence) { return "_pagesize"; //$NON-NLS-1$ From cfb47bce398b6592851be7b0cb4a2568a72c2e30 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Thu, 24 Oct 2024 11:31:47 -0400 Subject: [PATCH 9/9] Doc updates --- .../dynamic/sql/render/RenderingStrategy.java | 2 +- src/site/markdown/docs/springBatch.md | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java index fb10ea99b..6beb7bc24 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java @@ -37,7 +37,7 @@ public abstract class RenderingStrategy { public static final String DEFAULT_PARAMETER_PREFIX = "parameters"; //$NON-NLS-1$ /** - * Generate a unique key that can be used to place a parameter value in the parameter map + * Generate a unique key that can be used to place a parameter value in the parameter map. * * @param sequence a sequence for calculating a unique value * @return a key used to place the parameter value in the parameter map diff --git a/src/site/markdown/docs/springBatch.md b/src/site/markdown/docs/springBatch.md index 11ec90d6d..aca011c39 100644 --- a/src/site/markdown/docs/springBatch.md +++ b/src/site/markdown/docs/springBatch.md @@ -70,8 +70,8 @@ SelectStatementProvider selectStatement = SpringBatchUtility.selectForCursor(pe .render(); ``` -That utility method was limited in capability. The new method allows the full capabilities of the library. To migrate, -follow these steps: +That utility method was limited in capability and has been removed. The new method described above allows the full +capabilities of the library. To migrate, follow these steps: 1. Replace `SpringBatchUtility.selectForCursor(...)` with `SqlBuilder.select(...)` 2. Replace `render()` with `render(RenderingStrategies.MYBATIS3)` @@ -114,6 +114,26 @@ Notice the following important items: 2. The query must be rendered with the `SPRING_BATCH_PAGING_ITEM_READER_RENDERING_STRATEGY` rendering strategy. This rendering strategy will render the query so that it will respond properly to the runtime values supplied for page size and skip rows. +3. Note the use of `SpringBatchUtility.toParameterValues(...)`. This utility will set up the parameter Map correctly for + the rendered statement, and for use with a library supplied `@selectProvider`. See the following for an example of + the mapper method used for the query coded above: + +```java +@Mapper +public interface PersonMapper { + + @SelectProvider(type=SpringBatchProviderAdapter.class, method="select") + @Results({ + @Result(column="id", property="id", id=true), + @Result(column="first_name", property="firstName"), + @Result(column="last_name", property="lastName") + }) + List selectMany(Map parameterValues); +} +``` + +Note the use of the `SpringBatchProviderAdapter` - that adapter knows how to retrieve the rendered queries from the +parameter map initialed in the method above. ### Migrating from 1.x Support for MyBatisPagingItemReader @@ -128,9 +148,9 @@ SelectStatementProvider selectStatement = SpringBatchUtility.selectForPaging(pe .render(); ``` -That utility method was very limited in capability. It only supported limit and offset based queries - which are not -supported in all databases. The new method allows the full capabilities of the library. To migrate, -follow these steps: +That utility method was very limited in capability and has been removed. The prior method only supported limit and +offset based queries - which are not supported in all databases. The new method described above allows the full +capabilities of the library to be used. To migrate, follow these steps: 1. Replace `SpringBatchUtility.selectForPaging(...)` with `SqlBuilder.select(...)` 2. Add `limit()`, `fetchFirst()`, and `offset()` method calls as appropriate for your query and database