diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/Jdbc.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/Jdbc.java index ca7e716355..0ae81f9957 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/Jdbc.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/Jdbc.java @@ -26,6 +26,7 @@ import org.springframework.jdbc.core.JdbcTemplate; * Factory class for JDBC components. * * @author Jiandong Ma + * @author Artem Bilan * * @since 7.0 */ @@ -109,7 +110,9 @@ public final class Jdbc { * @param selectQuery the select query to build on * @return the {@link JdbcOutboundGatewaySpec} instance */ - public static JdbcOutboundGatewaySpec outboundGateway(JdbcOperations jdbcOperations, String updateQuery, String selectQuery) { + public static JdbcOutboundGatewaySpec outboundGateway(JdbcOperations jdbcOperations, String updateQuery, + String selectQuery) { + return new JdbcOutboundGatewaySpec(jdbcOperations, updateQuery, selectQuery); } @@ -119,7 +122,18 @@ public final class Jdbc { * @return the {@link JdbcStoredProcInboundChannelAdapterSpec} instance */ public static JdbcStoredProcInboundChannelAdapterSpec storedProcInboundAdapter(DataSource dataSource) { - return new JdbcStoredProcInboundChannelAdapterSpec(new StoredProcExecutor(dataSource)); + return new JdbcStoredProcInboundChannelAdapterSpec(storedProcExecutorSpec(dataSource)); + } + + /** + * The factory to produce a {@link JdbcStoredProcInboundChannelAdapterSpec}. + * @param storedProcExecutor the {@link StoredProcExecutor} to use + * @return the {@link JdbcStoredProcInboundChannelAdapterSpec} instance + */ + public static JdbcStoredProcInboundChannelAdapterSpec storedProcInboundAdapter( + StoredProcExecutor storedProcExecutor) { + + return new JdbcStoredProcInboundChannelAdapterSpec(storedProcExecutor); } /** @@ -128,7 +142,18 @@ public final class Jdbc { * @return the {@link JdbcStoredProcOutboundChannelAdapterSpec} instance */ public static JdbcStoredProcOutboundChannelAdapterSpec storedProcOutboundAdapter(DataSource dataSource) { - return new JdbcStoredProcOutboundChannelAdapterSpec(new StoredProcExecutor(dataSource)); + return new JdbcStoredProcOutboundChannelAdapterSpec(storedProcExecutorSpec(dataSource)); + } + + /** + * The factory to produce a {@link JdbcStoredProcOutboundChannelAdapterSpec}. + * @param storedProcExecutor the {@link StoredProcExecutor} to use + * @return the {@link JdbcStoredProcOutboundChannelAdapterSpec} instance + */ + public static JdbcStoredProcOutboundChannelAdapterSpec storedProcOutboundAdapter( + StoredProcExecutor storedProcExecutor) { + + return new JdbcStoredProcOutboundChannelAdapterSpec(storedProcExecutor); } /** @@ -137,7 +162,25 @@ public final class Jdbc { * @return the {@link JdbcStoredProcOutboundGatewaySpec} instance */ public static JdbcStoredProcOutboundGatewaySpec storedProcOutboundGateway(DataSource dataSource) { - return new JdbcStoredProcOutboundGatewaySpec(new StoredProcExecutor(dataSource)); + return new JdbcStoredProcOutboundGatewaySpec(storedProcExecutorSpec(dataSource)); + } + + /** + * The factory to produce a {@link JdbcStoredProcOutboundGatewaySpec}. + * @param storedProcExecutor the {@link StoredProcExecutor} to use + * @return the {@link JdbcStoredProcOutboundGatewaySpec} instance + */ + public static JdbcStoredProcOutboundGatewaySpec storedProcOutboundGateway(StoredProcExecutor storedProcExecutor) { + return new JdbcStoredProcOutboundGatewaySpec(storedProcExecutor); + } + + /** + * The factory to produce a {@link StoredProcExecutorSpec}. + * @param dataSource the {@link DataSource} to build on + * @return the {@link StoredProcExecutorSpec} instance + */ + public static StoredProcExecutorSpec storedProcExecutorSpec(DataSource dataSource) { + return new StoredProcExecutorSpec(dataSource); } private Jdbc() { diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcInboundChannelAdapterSpec.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcInboundChannelAdapterSpec.java index 611f5a95b2..ec05f24d0b 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcInboundChannelAdapterSpec.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcInboundChannelAdapterSpec.java @@ -27,10 +27,12 @@ import org.springframework.jdbc.core.namedparam.SqlParameterSource; * A {@link MessageSourceSpec} for a {@link JdbcInboundChannelAdapterSpec}. * * @author Jiandong Ma + * @author Artem Bilan * * @since 7.0 */ -public class JdbcInboundChannelAdapterSpec extends MessageSourceSpec { +public class JdbcInboundChannelAdapterSpec + extends MessageSourceSpec { protected JdbcInboundChannelAdapterSpec(JdbcOperations jdbcOperations, String selectQuery) { this.target = new JdbcPollingChannelAdapter(jdbcOperations, selectQuery); @@ -43,7 +45,7 @@ public class JdbcInboundChannelAdapterSpec extends MessageSourceSpec rowMapper) { this.target.setRowMapper(rowMapper); - return _this(); + return this; } /** @@ -53,7 +55,7 @@ public class JdbcInboundChannelAdapterSpec extends MessageSourceSpec { +public class JdbcOutboundChannelAdapterSpec + extends MessageHandlerSpec { protected JdbcOutboundChannelAdapterSpec(JdbcOperations jdbcOperations, String updateQuery) { this.target = new JdbcMessageHandler(jdbcOperations, updateQuery); @@ -42,7 +44,7 @@ public class JdbcOutboundChannelAdapterSpec extends MessageHandlerSpec rowMapper) { this.target.setRowMapper(rowMapper); - return _this(); + return this; } } diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcStoredProcInboundChannelAdapterSpec.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcStoredProcInboundChannelAdapterSpec.java index 30a6a30807..d9f93b464b 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcStoredProcInboundChannelAdapterSpec.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcStoredProcInboundChannelAdapterSpec.java @@ -20,6 +20,8 @@ import java.util.Collections; import java.util.Map; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + import org.springframework.integration.dsl.ComponentsRegistration; import org.springframework.integration.dsl.MessageSourceSpec; import org.springframework.integration.jdbc.StoredProcExecutor; @@ -30,6 +32,7 @@ import org.springframework.util.Assert; * A {@link MessageSourceSpec} for a {@link JdbcStoredProcInboundChannelAdapterSpec}. * * @author Jiandong Ma + * @author Artem Bilan * * @since 7.0 */ @@ -39,11 +42,16 @@ public class JdbcStoredProcInboundChannelAdapterSpec private final StoredProcExecutor storedProcExecutor; - private final StoredProcExecutorConfigurer storedProcExecutorConfigurer; + private @Nullable StoredProcExecutorSpec storedProcExecutorSpec; + + protected JdbcStoredProcInboundChannelAdapterSpec(StoredProcExecutorSpec storedProcExecutorSpec) { + this(storedProcExecutorSpec.getObject()); + this.storedProcExecutorSpec = storedProcExecutorSpec; + } protected JdbcStoredProcInboundChannelAdapterSpec(StoredProcExecutor storedProcExecutor) { this.storedProcExecutor = storedProcExecutor; - this.storedProcExecutorConfigurer = new StoredProcExecutorConfigurer(this.storedProcExecutor); + this.storedProcExecutorSpec = null; this.target = new StoredProcPollingChannelAdapter(this.storedProcExecutor); } @@ -52,10 +60,14 @@ public class JdbcStoredProcInboundChannelAdapterSpec * @param configurer the configurer. * @return the spec */ - public JdbcStoredProcInboundChannelAdapterSpec configurerStoredProcExecutor(Consumer configurer) { + public JdbcStoredProcInboundChannelAdapterSpec configurerStoredProcExecutor( + Consumer configurer) { + Assert.notNull(configurer, "'configurer' must not be null"); - configurer.accept(this.storedProcExecutorConfigurer); - return _this(); + Assert.notNull(this.storedProcExecutorSpec, + "The externally provided 'StoredProcExecutor' cannot be mutated in this spec"); + configurer.accept(this.storedProcExecutorSpec); + return this; } /** @@ -65,11 +77,12 @@ public class JdbcStoredProcInboundChannelAdapterSpec */ public JdbcStoredProcInboundChannelAdapterSpec expectSingleResult(boolean expectSingleResult) { this.target.setExpectSingleResult(expectSingleResult); - return _this(); + return this; } @Override public Map getComponentsToRegister() { return Collections.singletonMap(this.storedProcExecutor, null); } + } diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcStoredProcOutboundChannelAdapterSpec.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcStoredProcOutboundChannelAdapterSpec.java index d00bce59a0..3099937f66 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcStoredProcOutboundChannelAdapterSpec.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcStoredProcOutboundChannelAdapterSpec.java @@ -20,6 +20,8 @@ import java.util.Collections; import java.util.Map; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + import org.springframework.integration.dsl.ComponentsRegistration; import org.springframework.integration.dsl.MessageHandlerSpec; import org.springframework.integration.jdbc.StoredProcExecutor; @@ -30,6 +32,7 @@ import org.springframework.util.Assert; * A {@link MessageHandlerSpec} for a {@link JdbcStoredProcOutboundChannelAdapterSpec}. * * @author Jiandong Ma + * @author Artem Bilan * * @since 7.0 */ @@ -39,11 +42,16 @@ public class JdbcStoredProcOutboundChannelAdapterSpec private final StoredProcExecutor storedProcExecutor; - private final StoredProcExecutorConfigurer storedProcExecutorConfigurer; + private @Nullable StoredProcExecutorSpec storedProcExecutorSpec; + + protected JdbcStoredProcOutboundChannelAdapterSpec(StoredProcExecutorSpec storedProcExecutorSpec) { + this(storedProcExecutorSpec.getObject()); + this.storedProcExecutorSpec = storedProcExecutorSpec; + } protected JdbcStoredProcOutboundChannelAdapterSpec(StoredProcExecutor storedProcExecutor) { this.storedProcExecutor = storedProcExecutor; - this.storedProcExecutorConfigurer = new StoredProcExecutorConfigurer(this.storedProcExecutor); + this.storedProcExecutorSpec = null; this.target = new StoredProcMessageHandler(this.storedProcExecutor); } @@ -52,14 +60,19 @@ public class JdbcStoredProcOutboundChannelAdapterSpec * @param configurer the configurer. * @return the spec */ - public JdbcStoredProcOutboundChannelAdapterSpec configurerStoredProcExecutor(Consumer configurer) { + public JdbcStoredProcOutboundChannelAdapterSpec configurerStoredProcExecutor( + Consumer configurer) { + Assert.notNull(configurer, "'configurer' must not be null"); - configurer.accept(this.storedProcExecutorConfigurer); - return _this(); + Assert.notNull(this.storedProcExecutorSpec, + "The externally provided 'StoredProcExecutor' cannot be mutated in this spec"); + configurer.accept(this.storedProcExecutorSpec); + return this; } @Override public Map getComponentsToRegister() { return Collections.singletonMap(this.storedProcExecutor, null); } + } diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcStoredProcOutboundGatewaySpec.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcStoredProcOutboundGatewaySpec.java index dd7e97d900..a6aa2d4522 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcStoredProcOutboundGatewaySpec.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/JdbcStoredProcOutboundGatewaySpec.java @@ -20,6 +20,8 @@ import java.util.Collections; import java.util.Map; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + import org.springframework.integration.dsl.ComponentsRegistration; import org.springframework.integration.dsl.MessageHandlerSpec; import org.springframework.integration.jdbc.StoredProcExecutor; @@ -30,6 +32,7 @@ import org.springframework.util.Assert; * A {@link MessageHandlerSpec} for a {@link JdbcStoredProcOutboundGatewaySpec}. * * @author Jiandong Ma + * @author Artem Bilan * * @since 7.0 */ @@ -39,11 +42,16 @@ public class JdbcStoredProcOutboundGatewaySpec private final StoredProcExecutor storedProcExecutor; - private final StoredProcExecutorConfigurer storedProcExecutorConfigurer; + private @Nullable StoredProcExecutorSpec storedProcExecutorSpec; + + protected JdbcStoredProcOutboundGatewaySpec(StoredProcExecutorSpec storedProcExecutorSpec) { + this(storedProcExecutorSpec.getObject()); + this.storedProcExecutorSpec = storedProcExecutorSpec; + } protected JdbcStoredProcOutboundGatewaySpec(StoredProcExecutor storedProcExecutor) { this.storedProcExecutor = storedProcExecutor; - this.storedProcExecutorConfigurer = new StoredProcExecutorConfigurer(this.storedProcExecutor); + this.storedProcExecutorSpec = null; this.target = new StoredProcOutboundGateway(this.storedProcExecutor); } @@ -52,10 +60,12 @@ public class JdbcStoredProcOutboundGatewaySpec * @param configurer the configurer. * @return the spec */ - public JdbcStoredProcOutboundGatewaySpec configurerStoredProcExecutor(Consumer configurer) { + public JdbcStoredProcOutboundGatewaySpec configurerStoredProcExecutor(Consumer configurer) { Assert.notNull(configurer, "'configurer' must not be null"); - configurer.accept(this.storedProcExecutorConfigurer); - return _this(); + Assert.notNull(this.storedProcExecutorSpec, + "The externally provided 'StoredProcExecutor' cannot be mutated in this spec"); + configurer.accept(this.storedProcExecutorSpec); + return this; } /** @@ -65,7 +75,7 @@ public class JdbcStoredProcOutboundGatewaySpec */ public JdbcStoredProcOutboundGatewaySpec requiresReply(boolean requiresReply) { this.target.setRequiresReply(requiresReply); - return _this(); + return this; } /** @@ -75,11 +85,12 @@ public class JdbcStoredProcOutboundGatewaySpec */ public JdbcStoredProcOutboundGatewaySpec expectSingleResult(boolean expectSingleResult) { this.target.setExpectSingleResult(expectSingleResult); - return _this(); + return this; } @Override public Map getComponentsToRegister() { return Collections.singletonMap(this.storedProcExecutor, null); } + } diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/StoredProcExecutorConfigurer.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/StoredProcExecutorConfigurer.java deleted file mode 100644 index 509419899a..0000000000 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/StoredProcExecutorConfigurer.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 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. - * 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.springframework.integration.jdbc.dsl; - -import java.util.List; -import java.util.Map; - -import org.springframework.expression.Expression; -import org.springframework.integration.jdbc.SqlParameterSourceFactory; -import org.springframework.integration.jdbc.StoredProcExecutor; -import org.springframework.integration.jdbc.storedproc.ProcedureParameter; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.SqlParameter; - -/** - * A {@link StoredProcExecutor} configurer. - * - * @author Jiandong Ma - * - * @since 7.0 - */ -public class StoredProcExecutorConfigurer { - - private final StoredProcExecutor storedProcExecutor; - - protected StoredProcExecutorConfigurer(StoredProcExecutor storedProcExecutor) { - this.storedProcExecutor = storedProcExecutor; - } - - /** - * @param ignoreColumnMetaData the ignoreColumnMetaData - * @return the storedProcExecutor - * @see StoredProcExecutor#setIgnoreColumnMetaData(boolean) - */ - public StoredProcExecutorConfigurer ignoreColumnMetaData(boolean ignoreColumnMetaData) { - this.storedProcExecutor.setIgnoreColumnMetaData(ignoreColumnMetaData); - return this; - } - - /** - * @param procedureParameters the procedureParameters - * @return the storedProcExecutor - * @see StoredProcExecutor#setProcedureParameters(List) - */ - public StoredProcExecutorConfigurer procedureParameters(List procedureParameters) { - this.storedProcExecutor.setProcedureParameters(procedureParameters); - return this; - } - - /** - * @param sqlParameters the sqlParameters - * @return the storedProcExecutor - * @see StoredProcExecutor#setSqlParameters(List) - */ - public StoredProcExecutorConfigurer sqlParameters(List sqlParameters) { - this.storedProcExecutor.setSqlParameters(sqlParameters); - return this; - } - - /** - * @param sqlParameterSourceFactory the sqlParameterSourceFactory - * @return the storedProcExecutor - * @see StoredProcExecutor#setSqlParameterSourceFactory(SqlParameterSourceFactory) - */ - public StoredProcExecutorConfigurer sqlParameterSourceFactory(SqlParameterSourceFactory sqlParameterSourceFactory) { - this.storedProcExecutor.setSqlParameterSourceFactory(sqlParameterSourceFactory); - return this; - } - - /** - * @param storedProcedureName the storedProcedureName - * @return the storedProcExecutor - * @see StoredProcExecutor#setStoredProcedureName(String) - */ - public StoredProcExecutorConfigurer storedProcedureName(String storedProcedureName) { - this.storedProcExecutor.setStoredProcedureName(storedProcedureName); - return this; - } - - /** - * @param storedProcedureNameExpression the storedProcedureNameExpression - * @return the storedProcExecutor - * @see StoredProcExecutor#setStoredProcedureNameExpression(Expression) - */ - public StoredProcExecutorConfigurer storedProcedureNameExpression(Expression storedProcedureNameExpression) { - this.storedProcExecutor.setStoredProcedureNameExpression(storedProcedureNameExpression); - return this; - } - - /** - * @param usePayloadAsParameterSource the usePayloadAsParameterSource - * @return the storedProcExecutor - * @see StoredProcExecutor#setUsePayloadAsParameterSource(boolean) - */ - public StoredProcExecutorConfigurer usePayloadAsParameterSource(boolean usePayloadAsParameterSource) { - this.storedProcExecutor.setUsePayloadAsParameterSource(usePayloadAsParameterSource); - return this; - } - - /** - * @param isFunction the isFunction - * @return the storedProcExecutor - * @see StoredProcExecutor#setIsFunction(boolean) - */ - public StoredProcExecutorConfigurer isFunction(boolean isFunction) { - this.storedProcExecutor.setIsFunction(isFunction); - return this; - } - - /** - * @param returnValueRequired the returnValueRequired - * @return the storedProcExecutor - * @see StoredProcExecutor#setReturnValueRequired(boolean) - */ - public StoredProcExecutorConfigurer returnValueRequired(boolean returnValueRequired) { - this.storedProcExecutor.setReturnValueRequired(returnValueRequired); - return this; - } - - /** - * @param skipUndeclaredResults the skipUndeclaredResults - * @return the storedProcExecutor - * @see StoredProcExecutor#setSkipUndeclaredResults(boolean) - */ - public StoredProcExecutorConfigurer skipUndeclaredResults(boolean skipUndeclaredResults) { - this.storedProcExecutor.setSkipUndeclaredResults(skipUndeclaredResults); - return this; - } - - /** - * @param returningResultSetRowMappers the returningResultSetRowMappers - * @return the storedProcExecutor - * @see StoredProcExecutor#setReturningResultSetRowMappers(Map) - */ - public StoredProcExecutorConfigurer returningResultSetRowMappers(Map> returningResultSetRowMappers) { - this.storedProcExecutor.setReturningResultSetRowMappers(returningResultSetRowMappers); - return this; - } - - /** - * @param jdbcCallOperationsCacheSize the jdbcCallOperationsCacheSize - * @return the storedProcExecutor - * @see StoredProcExecutor#setJdbcCallOperationsCacheSize(int) - */ - public StoredProcExecutorConfigurer jdbcCallOperationsCacheSize(int jdbcCallOperationsCacheSize) { - this.storedProcExecutor.setJdbcCallOperationsCacheSize(jdbcCallOperationsCacheSize); - return this; - } -} diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/StoredProcExecutorSpec.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/StoredProcExecutorSpec.java new file mode 100644 index 0000000000..ec89ece349 --- /dev/null +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/StoredProcExecutorSpec.java @@ -0,0 +1,213 @@ +/* + * Copyright 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. + * 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.springframework.integration.jdbc.dsl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.expression.Expression; +import org.springframework.integration.dsl.IntegrationComponentSpec; +import org.springframework.integration.jdbc.SqlParameterSourceFactory; +import org.springframework.integration.jdbc.StoredProcExecutor; +import org.springframework.integration.jdbc.storedproc.ProcedureParameter; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.SqlParameter; + +/** + * A {@link StoredProcExecutor} configurer. + * + * @author Jiandong Ma + * @author Artem Bilan + * + * @since 7.0 + */ +public class StoredProcExecutorSpec extends IntegrationComponentSpec { + + private final List procedureParameters = new ArrayList<>(); + + private final List sqlParameters = new ArrayList<>(); + + private final Map> returningResultSetRowMappers = new HashMap<>(); + + protected StoredProcExecutorSpec(DataSource dataSource) { + this.target = new StoredProcExecutor(dataSource); + this.target.setReturningResultSetRowMappers(this.returningResultSetRowMappers); + } + + /** + * @param ignoreColumnMetaData the ignoreColumnMetaData + * @return the storedProcExecutor + * @see StoredProcExecutor#setIgnoreColumnMetaData(boolean) + */ + public StoredProcExecutorSpec ignoreColumnMetaData(boolean ignoreColumnMetaData) { + this.target.setIgnoreColumnMetaData(ignoreColumnMetaData); + return this; + } + + /** + * Add a {@link ProcedureParameter} parameter to the target {@link StoredProcExecutor}. + * This method can be called several times adding a new parameter each time. + * @param procedureParameter the stored procedure parameter to add + * @return the storedProcExecutor spec + * @see StoredProcExecutor#setProcedureParameters(List) + */ + public StoredProcExecutorSpec procedureParameter(ProcedureParameter procedureParameter) { + this.procedureParameters.add(procedureParameter); + this.target.setProcedureParameters(this.procedureParameters); + return this; + } + + /** + * @param procedureParameters the procedureParameters + * @return the storedProcExecutor + * @see StoredProcExecutor#setProcedureParameters(List) + */ + public StoredProcExecutorSpec procedureParameters(List procedureParameters) { + this.target.setProcedureParameters(procedureParameters); + return this; + } + + /** + * Add an {@link SqlParameter} to the target {@link StoredProcExecutor}. + * This method can be called several times adding a new parameter each time. + * @param sqlParameter the {@link SqlParameter} to add + * @return the storedProcExecutor + * @see StoredProcExecutor#setSqlParameters(List) + */ + public StoredProcExecutorSpec sqlParameter(SqlParameter sqlParameter) { + this.sqlParameters.add(sqlParameter); + this.target.setSqlParameters(this.sqlParameters); + return this; + } + + /** + * @param sqlParameters the sqlParameters + * @return the storedProcExecutor + * @see StoredProcExecutor#setSqlParameters(List) + */ + public StoredProcExecutorSpec sqlParameters(List sqlParameters) { + this.target.setSqlParameters(sqlParameters); + return this; + } + + /** + * @param sqlParameterSourceFactory the sqlParameterSourceFactory + * @return the storedProcExecutor + * @see StoredProcExecutor#setSqlParameterSourceFactory(SqlParameterSourceFactory) + */ + public StoredProcExecutorSpec sqlParameterSourceFactory(SqlParameterSourceFactory sqlParameterSourceFactory) { + this.target.setSqlParameterSourceFactory(sqlParameterSourceFactory); + return this; + } + + /** + * @param storedProcedureName the storedProcedureName + * @return the storedProcExecutor + * @see StoredProcExecutor#setStoredProcedureName(String) + */ + public StoredProcExecutorSpec storedProcedureName(String storedProcedureName) { + this.target.setStoredProcedureName(storedProcedureName); + return this; + } + + /** + * @param storedProcedureNameExpression the storedProcedureNameExpression + * @return the storedProcExecutor + * @see StoredProcExecutor#setStoredProcedureNameExpression(Expression) + */ + public StoredProcExecutorSpec storedProcedureNameExpression(Expression storedProcedureNameExpression) { + this.target.setStoredProcedureNameExpression(storedProcedureNameExpression); + return this; + } + + /** + * @param usePayloadAsParameterSource the usePayloadAsParameterSource + * @return the storedProcExecutor + * @see StoredProcExecutor#setUsePayloadAsParameterSource(boolean) + */ + public StoredProcExecutorSpec usePayloadAsParameterSource(boolean usePayloadAsParameterSource) { + this.target.setUsePayloadAsParameterSource(usePayloadAsParameterSource); + return this; + } + + /** + * @param isFunction the isFunction + * @return the storedProcExecutor + * @see StoredProcExecutor#setIsFunction(boolean) + */ + public StoredProcExecutorSpec isFunction(boolean isFunction) { + this.target.setIsFunction(isFunction); + return this; + } + + /** + * @param returnValueRequired the returnValueRequired + * @return the storedProcExecutor + * @see StoredProcExecutor#setReturnValueRequired(boolean) + */ + public StoredProcExecutorSpec returnValueRequired(boolean returnValueRequired) { + this.target.setReturnValueRequired(returnValueRequired); + return this; + } + + /** + * @param skipUndeclaredResults the skipUndeclaredResults + * @return the storedProcExecutor + * @see StoredProcExecutor#setSkipUndeclaredResults(boolean) + */ + public StoredProcExecutorSpec skipUndeclaredResults(boolean skipUndeclaredResults) { + this.target.setSkipUndeclaredResults(skipUndeclaredResults); + return this; + } + + /** + * Add a {@link RowMapper} for specific returning result from stored procedure execution. + * @param returningResultName the name of procedure output parameter for returning result + * @param rowMapper the {@link RowMapper} for returning result + * @return the storedProcExecutor + * @see StoredProcExecutor#setReturningResultSetRowMappers(Map) + */ + public StoredProcExecutorSpec returningResultSetRowMapper(String returningResultName, RowMapper rowMapper) { + this.returningResultSetRowMappers.put(returningResultName, rowMapper); + return this; + } + + /** + * @param returningResultSetRowMappers the returningResultSetRowMappers + * @return the storedProcExecutor + * @see StoredProcExecutor#setReturningResultSetRowMappers(Map) + */ + public StoredProcExecutorSpec returningResultSetRowMappers(Map> returningResultSetRowMappers) { + this.target.setReturningResultSetRowMappers(returningResultSetRowMappers); + return this; + } + + /** + * @param jdbcCallOperationsCacheSize the jdbcCallOperationsCacheSize + * @return the storedProcExecutor + * @see StoredProcExecutor#setJdbcCallOperationsCacheSize(int) + */ + public StoredProcExecutorSpec jdbcCallOperationsCacheSize(int jdbcCallOperationsCacheSize) { + this.target.setJdbcCallOperationsCacheSize(jdbcCallOperationsCacheSize); + return this; + } + +} diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/package-info.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/package-info.java new file mode 100644 index 0000000000..7f2c44178c --- /dev/null +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/dsl/package-info.java @@ -0,0 +1,4 @@ +/** + * Java DSL classes for JDBC module. + */ +package org.springframework.integration.jdbc.dsl; diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/dsl/JdbcTest.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/dsl/JdbcTests.java similarity index 81% rename from spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/dsl/JdbcTest.java rename to spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/dsl/JdbcTests.java index 2e2eee6fcd..a64f53be81 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/dsl/JdbcTest.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/dsl/JdbcTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2025 the original author or authors. + * Copyright 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. @@ -24,6 +24,7 @@ import java.util.Map; import javax.sql.DataSource; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -37,6 +38,7 @@ import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.expression.ValueExpression; import org.springframework.integration.jdbc.BeanPropertySqlParameterSourceFactory; import org.springframework.integration.jdbc.ExpressionEvaluatingSqlParameterSourceFactory; +import org.springframework.integration.jdbc.StoredProcExecutor; import org.springframework.integration.jdbc.config.JdbcTypesEnum; import org.springframework.integration.jdbc.storedproc.ClobSqlReturnType; import org.springframework.integration.jdbc.storedproc.PrimeMapper; @@ -48,7 +50,6 @@ import org.springframework.integration.support.json.JsonInboundMessageMapper; import org.springframework.integration.support.json.JsonOutboundMessageMapper; import org.springframework.integration.test.util.OnlyOnceTrigger; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.SqlOutParameter; import org.springframework.jdbc.core.SqlParameter; import org.springframework.jdbc.core.SqlReturnType; @@ -68,12 +69,13 @@ import static org.mockito.Mockito.mock; /** * @author Jiandong Ma + * @author Artem Bilan * * @since 7.0 */ @SpringJUnitConfig @DirtiesContext -class JdbcTest { +class JdbcTests { @Autowired private JdbcTemplate h2JdbcTemplate; @@ -123,27 +125,26 @@ class JdbcTest { void testInboundFlow() { Message message = this.inboundFlowPollerChannel.receive(10_000); List rows = (List) message.getPayload(); - assertThat(rows.size()).isEqualTo(2); - assertThat(rows.get(0) instanceof Inbound).isTrue(); - Inbound item = (Inbound) rows.get(0); - assertThat(item.id()).isEqualTo(1); - assertThat(item.status()).isEqualTo(2); + assertThat(rows).hasSize(2); + assertThat(rows.get(0)) + .asInstanceOf(InstanceOfAssertFactories.type(Inbound.class)) + .hasFieldOrPropertyWithValue("id", 1) + .hasFieldOrPropertyWithValue("status", 2); - Integer countOfStatusTwo = h2JdbcTemplate.queryForObject("select count(*) from inbound where status = 2", Integer.class); + Integer countOfStatusTwo = + h2JdbcTemplate.queryForObject("select count(*) from inbound where status = 2", Integer.class); assertThat(countOfStatusTwo).isEqualTo(0); - Integer countOfStatusTen = h2JdbcTemplate.queryForObject("select count(*) from inbound where status = 10", Integer.class); + Integer countOfStatusTen = + h2JdbcTemplate.queryForObject("select count(*) from inbound where status = 10", Integer.class); assertThat(countOfStatusTen).isEqualTo(2); } - record Inbound(int id, int status) { - } - @Test void testOutboundFlow() { outboundFlowInputChannel.send(new GenericMessage<>("foo")); Map map = h2JdbcTemplate.queryForMap("select * from outbound where id=?", 1); - assertThat(map.get("name")).isEqualTo("foo"); + assertThat(map).containsEntry("name", "foo"); } @Test @@ -154,8 +155,9 @@ class JdbcTest { List payload = (List) message.getPayload(); assertThat(payload).hasSize(1); Object item = payload.get(0); - assertThat(item).isInstanceOf(Map.class); - assertThat(((Map) item).get("status")).isEqualTo(10); + assertThat(item) + .asInstanceOf(InstanceOfAssertFactories.map(String.class, Integer.class)) + .containsEntry("status", 10); } @Test @@ -164,8 +166,9 @@ class JdbcTest { Message message = outboundGatewayNoSelectQueryReplyChannel.receive(10_000); assertThat(message).isNotNull(); Object payload = message.getPayload(); - assertThat(payload).isInstanceOf(Map.class); - assertThat(((Map) payload).get("UPDATED")).isEqualTo(1); + assertThat(payload) + .asInstanceOf(InstanceOfAssertFactories.map(String.class, Integer.class)) + .containsEntry("UPDATED", 1); } @Test @@ -174,20 +177,17 @@ class JdbcTest { assertThat(message).isNotNull(); Object payload = message.getPayload(); assertThat(payload).isNotNull(); - assertThat(payload).isInstanceOf(List.class); - - List primeNumbers = (List) payload; - - assertThat(primeNumbers.size() == 4).isTrue(); + assertThat(payload).asInstanceOf(InstanceOfAssertFactories.list(Integer.class)).hasSize(4); } @Test void testStoredProcOutboundFlow() { - storedProcOutboundFlowInputChannel.send(MessageBuilder.withPayload(new User("username", "password", "email")).build()); + storedProcOutboundFlowInputChannel.send(new GenericMessage<>(new User("username", "password", "email"))); Map map = this.derbyJdbcTemplate.queryForMap("SELECT * FROM USERS WHERE USERNAME=?", "username"); - assertThat(map.get("USERNAME")).as("Wrong username").isEqualTo("username"); - assertThat(map.get("PASSWORD")).as("Wrong password").isEqualTo("password"); - assertThat(map.get("EMAIL")).as("Wrong email").isEqualTo("email"); + assertThat(map) + .containsEntry("USERNAME", "username") + .containsEntry("PASSWORD", "password") + .containsEntry("EMAIL", "email"); } @Test @@ -204,7 +204,7 @@ class JdbcTest { assertThat(resultMessage).isNotNull(); Object resultPayload = resultMessage.getPayload(); - assertThat(resultPayload instanceof String).isTrue(); + assertThat(resultPayload).isInstanceOf(String.class); Message message = new JsonInboundMessageMapper(String.class, new Jackson2JsonMessageParser()) .toMessage((String) resultPayload); assertThat(message.getPayload()).isEqualTo(testMessage.getPayload()); @@ -224,7 +224,7 @@ class JdbcTest { sqlParameterSourceFactory.setBeanFactory(mock()); return IntegrationFlow.from(Jdbc.inboundAdapter(h2DataSource, "select * from inbound") .maxRows(2) - .rowMapper((RowMapper) (rs, rowNum) -> new Inbound(rs.getInt(1), rs.getInt(2))) + .rowMapper((rs, rowNum) -> new Inbound(rs.getInt(1), rs.getInt(2))) .updateSql("update inbound set status = 10 where id in (:id)") .updatePerRow(false) .updateSqlParameterSourceFactory(sqlParameterSourceFactory) @@ -237,14 +237,12 @@ class JdbcTest { @Bean public IntegrationFlow outboundFlow(DataSource h2DataSource) { return flow -> flow - .handle(Jdbc.outboundAdapter(h2DataSource, "insert into outbound (id, status, name) values (1, 0, ?)") - .preparedStatementSetter((ps, requestMessage) -> { - ps.setObject(1, requestMessage.getPayload()); - }) + .handle(Jdbc.outboundAdapter(h2DataSource, + "insert into outbound (id, status, name) values (1, 0, ?)") + .preparedStatementSetter((ps, requestMessage) -> + ps.setObject(1, requestMessage.getPayload())) .usePayloadAsParameterSource(false) - .sqlParameterSourceFactory(null) - .keysGenerated(false) - ); + .keysGenerated(false)); } @Bean @@ -280,15 +278,11 @@ class JdbcTest { .ignoreColumnMetaData(true) .isFunction(false) .storedProcedureName("GET_PRIME_NUMBERS") - .procedureParameters(List.of( - new ProcedureParameter("beginRange", 1, null), - new ProcedureParameter("endRange", 10, null) - )) - .sqlParameters(List.of( - new SqlParameter("beginRange", Types.INTEGER), - new SqlParameter("endRange", Types.INTEGER) - )) - .returningResultSetRowMappers(Map.of("out", new PrimeMapper())) + .procedureParameter(new ProcedureParameter("beginRange", 1, null)) + .procedureParameter(new ProcedureParameter("endRange", 10, null)) + .sqlParameter(new SqlParameter("beginRange", Types.INTEGER)) + .sqlParameter(new SqlParameter("endRange", Types.INTEGER)) + .returningResultSetRowMapper("out", new PrimeMapper()) ), e -> e.poller(p -> p.trigger(new OnlyOnceTrigger()))) .channel(c -> c.queue("storedProcInboundPollerChannel")) @@ -296,19 +290,21 @@ class JdbcTest { } @Bean - public IntegrationFlow storedProcOutboundAdapter(DataSource derbyDataSource) { - return flow -> flow - .handle(Jdbc.storedProcOutboundAdapter(derbyDataSource) - .configurerStoredProcExecutor(configurer -> configurer - .storedProcedureName("CREATE_USER") - .sqlParameterSourceFactory(new BeanPropertySqlParameterSourceFactory()) - .usePayloadAsParameterSource(true) - ) - ); + StoredProcExecutorSpec storedProcExecutor(DataSource derbyDataSource) { + return Jdbc.storedProcExecutorSpec(derbyDataSource) + .storedProcedureName("CREATE_USER") + .sqlParameterSourceFactory(new BeanPropertySqlParameterSourceFactory()) + .usePayloadAsParameterSource(true); } @Bean - public IntegrationFlow storedProcOutboundGateway(DataSource derbyDataSource) throws Exception { + public IntegrationFlow storedProcOutboundAdapter(StoredProcExecutor storedProcExecutor) { + return flow -> flow + .handle(Jdbc.storedProcOutboundAdapter(storedProcExecutor)); + } + + @Bean + public IntegrationFlow storedProcOutboundGateway(DataSource derbyDataSource) { return flow -> flow .handle(Jdbc.storedProcOutboundGateway(derbyDataSource) @@ -372,6 +368,11 @@ class JdbcTest { public PlatformTransactionManager derbyTransactionManager() { return new DataSourceTransactionManager(derbyDataSource()); } + + } + + record Inbound(int id, int status) { + } } diff --git a/spring-integration-jdbc/src/test/resources/dsl-h2.sql b/spring-integration-jdbc/src/test/resources/dsl-h2.sql index f42fb973cc..631fa98f51 100644 --- a/spring-integration-jdbc/src/test/resources/dsl-h2.sql +++ b/spring-integration-jdbc/src/test/resources/dsl-h2.sql @@ -1,10 +1,31 @@ -create table inbound(id int,status int); -insert into inbound values(1,2); -insert into inbound values(2,2); +create table inbound +( + id int, + status int +); -create table outbound(id varchar(100),status int,name varchar(20)); +insert into inbound +values (1, 2); -create table outbound_gateway(id int,status int); -insert into outbound_gateway values(1,2); -insert into outbound_gateway values(2,2); +insert into inbound +values (2, 2); + +create table outbound +( + id varchar(100), + status int, + name varchar(20) +); + +create table outbound_gateway +( + id int, + status int +); + +insert into outbound_gateway +values (1, 2); + +insert into outbound_gateway +values (2, 2); diff --git a/src/reference/antora/modules/ROOT/nav.adoc b/src/reference/antora/modules/ROOT/nav.adoc index 9bb2808e5b..ed0e24fff8 100644 --- a/src/reference/antora/modules/ROOT/nav.adoc +++ b/src/reference/antora/modules/ROOT/nav.adoc @@ -184,6 +184,7 @@ ** xref:jdbc/outbound-gateway.adoc[] ** xref:jdbc/message-store.adoc[] ** xref:jdbc/stored-procedures.adoc[] +** xref:jdbc/dsl.adoc[] ** xref:jdbc/lock-registry.adoc[] ** xref:jdbc/metadata-store.adoc[] * xref:jpa.adoc[] diff --git a/src/reference/antora/modules/ROOT/pages/jdbc/dsl.adoc b/src/reference/antora/modules/ROOT/pages/jdbc/dsl.adoc new file mode 100644 index 0000000000..786f79cd34 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/jdbc/dsl.adoc @@ -0,0 +1,61 @@ +[[jdbc-dsl]] += Java DSL for JDBC Components + +Version 7.0 introduced the Java DSL API for channel adapters in JDBC module. +The central Java DSL class (and usually starting point) is a `org.springframework.integration.jdbc.dsl.Jdbc` factory. +It provides self-explanatory method to initiate configuration for target channel adapter or gateway. +The standard `IntegrationComponentSpec` implementations for out-of-the-box channel adapters are: + +* `JdbcInboundChannelAdapterSpec extends MessageSourceSpec` +* `JdbcOutboundChannelAdapterSpec extends MessageHandlerSpec` +* `JdbcOutboundGatewaySpec extends MessageHandlerSpec` +* `JdbcStoredProcInboundChannelAdapterSpec extends MessageSourceSpec` +* `JdbcStoredProcOutboundChannelAdapterSpec extends MessageHandlerSpec` +* `JdbcStoredProcOutboundGatewaySpec extends MessageHandlerSpec` + +In addition, the `StoredProcExecutorSpec`, a convenient, builder-like component is provided for a `StoredProcExecutor` creation and configuration. + +Here are some examples how `Jdbc` factory can be used for configuration an `IntegrationFlow`: + +[source, java] +---- +@Bean +public DataSource h2DataSource() { + return new EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.H2) + .addScripts("classpath:dsl-h2.sql", "classpath:h2-stored-procedures.sql") + .build(); +} + +@Bean +public IntegrationFlow outboundFlow(DataSource h2DataSource) { + return flow -> flow + .handle(Jdbc.outboundAdapter(h2DataSource, + "insert into outbound (id, status, name) values (1, 0, ?)") + .preparedStatementSetter((ps, requestMessage) -> + ps.setObject(1, requestMessage.getPayload())) + .usePayloadAsParameterSource(false) + .keysGenerated(false)); +} + +@Bean +public IntegrationFlow storedProcInboundFlow(DataSource h2DataSource) { + return IntegrationFlow.from(Jdbc.storedProcInboundAdapter(h2DataSource) + .expectSingleResult(true) + .configurerStoredProcExecutor(configurer -> configurer + .ignoreColumnMetaData(true) + .isFunction(false) + .storedProcedureName("GET_PRIME_NUMBERS") + .procedureParameter(new ProcedureParameter("beginRange", 1, null)) + .procedureParameter(new ProcedureParameter("endRange", 10, null)) + .sqlParameter(new SqlParameter("beginRange", Types.INTEGER)) + .sqlParameter(new SqlParameter("endRange", Types.INTEGER)) + .returningResultSetRowMapper("out", new PrimeMapper()) + ), + e -> e.poller(p -> p.trigger(new OnlyOnceTrigger()))) + .channel(c -> c.queue("storedProcInboundPollerChannel")) + .get(); +} +---- + +This Java DSL API can be used as is with the xref:kotlin-dsl.adoc[Kotlin] and xref:groovy-dsl.adoc[Groovy] DSLs. \ No newline at end of file diff --git a/src/reference/antora/modules/ROOT/pages/whats-new.adoc b/src/reference/antora/modules/ROOT/pages/whats-new.adoc index b0cf410a9c..8c646414e7 100644 --- a/src/reference/antora/modules/ROOT/pages/whats-new.adoc +++ b/src/reference/antora/modules/ROOT/pages/whats-new.adoc @@ -17,3 +17,8 @@ Java 17 is still baseline, but Java 24 is supported. [[x7.0-general]] == General Changes +[[x7.0-new-components]] +== New Components + +The JDBC module now provides a Java DSL API via its dedicated `org.springframework.integration.jdbc.dsl.Jdbc` factory. +The xref:jdbc/dsl.adoc[] chapter provides more details. \ No newline at end of file