diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc index 145d2252833..d289fc0ae6e 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc @@ -679,17 +679,30 @@ provides `firstName` and `lastName` properties, such as the `Actor` class from a ---- For batch updates, accumulate the batch entries in a fluent fashion through `batch()`, -binding the parameters for each entry as for a single update – either as positional -parameters or as named parameters – and separating consecutive entries with `add()`. -The accumulated entries are executed as a single JDBC batch by `update()`, which -also completes the final entry implicitly: +defining each entry through `entry(...)` – either with a callback that binds positional +or named parameters as for a single update, or with a `List` of positional parameters +or a `Map` of named parameters. The accumulated entries are executed as a single JDBC +batch by `update()`: [source,java,indent=0,subs="verbatim,quotes"] ---- this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)") .batch() - .param("firstName", "Leonor").param("lastName", "Watling").add() - .param("firstName", "Christian").param("lastName", "Bale") + .entry(entry -> entry.param("firstName", "Leonor").param("lastName", "Watling")) + .entry(entry -> entry.param("firstName", "Christian").param("lastName", "Bale")) + .update(); +---- + +Alternatively, define several entries at once through `entries(...)`, with each given +parameter source object – for example, an `Actor` record – representing one entry. +`entry(...)` and `entries(...)` calls may be freely combined within the same batch: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + List actors = ...; + this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)") + .batch() + .entries(actors) .update(); ---- diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/DefaultJdbcClient.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/DefaultJdbcClient.java index 7e3ebc782bf..1cb015a1424 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/DefaultJdbcClient.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/DefaultJdbcClient.java @@ -19,10 +19,12 @@ package org.springframework.jdbc.core.simple; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; import java.util.stream.Stream; import javax.sql.DataSource; @@ -337,6 +339,16 @@ final class DefaultJdbcClient implements JdbcClient { } } + @SuppressWarnings({"rawtypes", "unchecked"}) + private static SqlParameterSource toSqlParameterSource(Object namedParamObject) { + if (namedParamObject instanceof SqlParameterSource sqlParameterSource) { + return sqlParameterSource; + } + return (namedParamObject instanceof Map map ? + new MapSqlParameterSource(map) : + new SimplePropertySqlParameterSource(namedParamObject)); + } + private class DefaultBatchSpec implements BatchSpec { @@ -346,84 +358,45 @@ final class DefaultJdbcClient implements JdbcClient { private @Nullable Boolean usingNamedParams; - private List<@Nullable Object> currentIndexedParams = new ArrayList<>(); - - private MapSqlParameterSource currentNamedParams = new MapSqlParameterSource(); - - private SqlParameterSource currentNamedParamSource = this.currentNamedParams; - @Override - public BatchSpec param(@Nullable Object value) { - validateIndexedParamValue(value); - this.currentIndexedParams.add(value); + public BatchSpec entry(Consumer entryConsumer) { + DefaultBatchEntry entry = new DefaultBatchEntry(); + entryConsumer.accept(entry); + addEntry(entry); return this; } @Override - public BatchSpec param(int jdbcIndex, @Nullable Object value) { - addIndexedParam(this.currentIndexedParams, jdbcIndex, value); + public BatchSpec entry(List values) { + DefaultBatchEntry entry = new DefaultBatchEntry(); + entry.indexedParams.addAll(values); + addEntry(entry); return this; } @Override - public BatchSpec param(int jdbcIndex, @Nullable Object value, int sqlType) { - return param(jdbcIndex, new SqlParameterValue(sqlType, value)); - } - - @Override - public BatchSpec param(String name, @Nullable Object value) { - this.currentNamedParams.addValue(name, value); + public BatchSpec entry(Map paramMap) { + DefaultBatchEntry entry = new DefaultBatchEntry(); + entry.namedParams.addValues(paramMap); + addEntry(entry); return this; } @Override - public BatchSpec param(String name, @Nullable Object value, int sqlType) { - this.currentNamedParams.addValue(name, value, sqlType); - return this; + public BatchSpec entries(Object... namedParamObjects) { + return entries(Arrays.asList(namedParamObjects)); } @Override - public BatchSpec params(Object... values) { - Collections.addAll(this.currentIndexedParams, values); - return this; - } - - @Override - public BatchSpec params(List values) { - this.currentIndexedParams.addAll(values); - return this; - } - - @Override - public BatchSpec params(Map paramMap) { - this.currentNamedParams.addValues(paramMap); - return this; - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - @Override - public BatchSpec paramSource(Object namedParamObject) { - this.currentNamedParamSource = (namedParamObject instanceof Map map ? - new MapSqlParameterSource(map) : - new SimplePropertySqlParameterSource(namedParamObject)); - return this; - } - - @Override - public BatchSpec paramSource(SqlParameterSource namedParamSource) { - this.currentNamedParamSource = namedParamSource; - return this; - } - - @Override - public BatchSpec add() { - completeEntry(); + public BatchSpec entries(List namedParamObjects) { + for (Object namedParamObject : namedParamObjects) { + addNamedEntry(toSqlParameterSource(namedParamObject)); + } return this; } @Override public int[] update() { - completeEntry(); return (Boolean.TRUE.equals(this.usingNamedParams) ? namedParamOps.batchUpdate(sql, this.namedBatch.toArray(new SqlParameterSource[0])) : classicOps.batchUpdate(sql, this.indexedBatch)); @@ -440,7 +413,6 @@ final class DefaultJdbcClient implements JdbcClient { } private int[] doUpdate(KeyHolder generatedKeyHolder, String @Nullable [] keyColumnNames) { - completeEntry(); if (Boolean.TRUE.equals(this.usingNamedParams)) { if (keyColumnNames != null) { return namedParamOps.batchUpdate(sql, this.namedBatch.toArray(new SqlParameterSource[0]), @@ -481,36 +453,97 @@ final class DefaultJdbcClient implements JdbcClient { } } - private void completeEntry() { - boolean hasIndexed = !this.currentIndexedParams.isEmpty(); - boolean hasNamed = (this.currentNamedParams.hasValues() || - this.currentNamedParamSource != this.currentNamedParams); + private void addEntry(DefaultBatchEntry entry) { + boolean hasIndexed = !entry.indexedParams.isEmpty(); + boolean hasNamed = (entry.namedParams.hasValues() || entry.namedParamSource != entry.namedParams); if (hasIndexed && hasNamed) { throw new IllegalStateException("Configure either named or indexed parameters, not both"); } - if (this.currentNamedParams.hasValues() && this.currentNamedParamSource != this.currentNamedParams) { + if (entry.namedParams.hasValues() && entry.namedParamSource != entry.namedParams) { throw new IllegalStateException( "Configure either individual named parameters or a SqlParameterSource, not both"); } - if (!hasIndexed && !hasNamed) { - return; + if (hasNamed) { + addNamedEntry(entry.namedParamSource); } + else if (hasIndexed) { + addIndexedEntry(entry.indexedParams.toArray()); + } + else { + throw new IllegalStateException("Configure at least one parameter for each batch entry"); + } + } + + private void addNamedEntry(SqlParameterSource namedParamSource) { + enforceParamStyle(true); + this.namedBatch.add(namedParamSource); + } + + private void addIndexedEntry(@Nullable Object[] indexedParams) { + enforceParamStyle(false); + this.indexedBatch.add(indexedParams); + } + + private void enforceParamStyle(boolean named) { if (this.usingNamedParams == null) { - this.usingNamedParams = hasNamed; + this.usingNamedParams = named; } - else if (this.usingNamedParams != hasNamed) { + else if (this.usingNamedParams != named) { throw new IllegalStateException( "Configure either named or indexed parameters for all batch entries, not both"); } - if (hasNamed) { - this.namedBatch.add(this.currentNamedParamSource); - this.currentNamedParams = new MapSqlParameterSource(); - this.currentNamedParamSource = this.currentNamedParams; - } - else { - this.indexedBatch.add(this.currentIndexedParams.toArray()); - this.currentIndexedParams = new ArrayList<>(); - } + } + } + + + private static class DefaultBatchEntry implements BatchEntry { + + private final List<@Nullable Object> indexedParams = new ArrayList<>(); + + private final MapSqlParameterSource namedParams = new MapSqlParameterSource(); + + private SqlParameterSource namedParamSource = this.namedParams; + + @Override + public BatchEntry param(@Nullable Object value) { + validateIndexedParamValue(value); + this.indexedParams.add(value); + return this; + } + + @Override + public BatchEntry param(int jdbcIndex, @Nullable Object value) { + addIndexedParam(this.indexedParams, jdbcIndex, value); + return this; + } + + @Override + public BatchEntry param(int jdbcIndex, @Nullable Object value, int sqlType) { + return param(jdbcIndex, new SqlParameterValue(sqlType, value)); + } + + @Override + public BatchEntry param(String name, @Nullable Object value) { + this.namedParams.addValue(name, value); + return this; + } + + @Override + public BatchEntry param(String name, @Nullable Object value, int sqlType) { + this.namedParams.addValue(name, value, sqlType); + return this; + } + + @Override + public BatchEntry paramSource(Object namedParamObject) { + this.namedParamSource = toSqlParameterSource(namedParamObject); + return this; + } + + @Override + public BatchEntry paramSource(SqlParameterSource namedParamSource) { + this.namedParamSource = namedParamSource; + return this; } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/JdbcClient.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/JdbcClient.java index 4c77562b748..12351cc3ba6 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/JdbcClient.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/JdbcClient.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import java.util.stream.Stream; import javax.sql.DataSource; @@ -351,22 +352,27 @@ public interface JdbcClient { /** * Begin a batch update for the provided SQL statement, accumulating several - * sets of parameters with each set representing the parameters for one + * batch entries with each entry representing the parameters for one * statement execution within the batch. - *

Bind the parameters for each batch entry through the returned - * {@link BatchSpec} in the same fashion as for a single update, separating - * consecutive entries with {@link BatchSpec#add()}, and finally trigger - * execution through {@link BatchSpec#update()}: + *

Define each batch entry through the returned {@link BatchSpec} and + * finally trigger execution through {@link BatchSpec#update()}: *

 		 * int[] rowsAffected = client.sql("INSERT INTO user (first_name, last_name) VALUES (:first, :last)")
 		 *     .batch()
-		 *         .param("first", "Jane").param("last", "Smith").add()
-		 *         .param("first", "John").param("last", "Doe")
-		 *     .batchUpdate();
+		 *         .entry(entry -> entry.param("first", "Jane").param("last", "Smith"))
+		 *         .entry(entry -> entry.param("first", "John").param("last", "Doe"))
+		 *     .update();
 		 * 
- *

The final batch entry does not need to be marked with {@code add()}; - * {@link BatchSpec#update()} completes it implicitly. - * @return a batch specification for accumulating sets of parameters + *

Alternatively, provide an entire list of parameter objects at once, + * with each object representing one batch entry: + *

+		 * List<User> users = ...;
+		 * int[] rowsAffected = client.sql("INSERT INTO user (first_name, last_name) VALUES (:firstName, :lastName)")
+		 *     .batch()
+		 *         .entries(users)
+		 *     .update();
+		 * 
+ * @return a batch specification for accumulating batch entries * @since 7.1 * @see java.sql.PreparedStatement#executeBatch() */ @@ -375,149 +381,92 @@ public interface JdbcClient { /** - * A specification for accumulating several sets of parameters for a batch - * update, with each set representing the parameters for one statement - * execution within the batch. + * A specification for accumulating several batch entries for a batch update, + * with each entry representing the parameters for one statement execution + * within the batch. * - *

Parameters are bound in the same fashion as for a single {@link StatementSpec}, + *

Each {@code entry(...)} or {@code entries(...)} call defines one or more + * complete batch entries; calls may be freely combined within the same batch. + * Parameters are bound in the same fashion as for a single {@link StatementSpec}, * either as JDBC-style positional parameters or as Spring-style named parameters - * (but not both within the same batch). A batch entry is completed and a new one - * started through {@link #add()}; the final entry is completed implicitly by - * {@link #update()}. + * (but not both within the same batch). * * @since 7.1 * @see StatementSpec#batch() + * @see BatchEntry */ interface BatchSpec { /** - * Bind a positional JDBC statement parameter for "?" placeholder resolution - * by implicit order of parameter value registration, for the current batch entry. - *

This is primarily intended for statements with a single parameter - * or very few parameters, registering each parameter value in the order - * of the parameter's occurrence in the SQL statement. - * @param value the parameter value to bind + * Define a batch entry by binding its parameters through the given callback. + *

The entry is complete once the callback returns. + * @param entryConsumer a callback for binding the parameters of the entry * @return this batch specification (for chaining) - * @see StatementSpec#param(Object) + * @throws IllegalStateException if the entry does not declare any parameters, + * or if named and indexed parameters are mixed within the batch */ - BatchSpec param(@Nullable Object value); + BatchSpec entry(Consumer entryConsumer); /** - * Bind a positional JDBC statement parameter for "?" placeholder resolution - * by explicit JDBC statement parameter index, for the current batch entry. - * @param jdbcIndex the JDBC-style index (starting with 1) - * @param value the parameter value to bind - * @return this batch specification (for chaining) - * @see StatementSpec#param(int, Object) - */ - BatchSpec param(int jdbcIndex, @Nullable Object value); - - /** - * Bind a positional JDBC statement parameter for "?" placeholder resolution - * by explicit JDBC statement parameter index, for the current batch entry. - * @param jdbcIndex the JDBC-style index (starting with 1) - * @param value the parameter value to bind - * @param sqlType the associated SQL type (see {@link java.sql.Types}) - * @return this batch specification (for chaining) - * @see StatementSpec#param(int, Object, int) - */ - BatchSpec param(int jdbcIndex, @Nullable Object value, int sqlType); - - /** - * Bind a named statement parameter for ":x" placeholder resolution, - * with each "x" name matching a ":x" placeholder in the SQL statement, - * for the current batch entry. - * @param name the parameter name - * @param value the parameter value to bind - * @return this batch specification (for chaining) - * @see StatementSpec#param(String, Object) - */ - BatchSpec param(String name, @Nullable Object value); - - /** - * Bind a named statement parameter for ":x" placeholder resolution, - * with each "x" name matching a ":x" placeholder in the SQL statement, - * for the current batch entry. - * @param name the parameter name - * @param value the parameter value to bind - * @param sqlType the associated SQL type (see {@link java.sql.Types}) - * @return this batch specification (for chaining) - * @see StatementSpec#param(String, Object, int) - */ - BatchSpec param(String name, @Nullable Object value, int sqlType); - - /** - * Bind a var-args list of positional parameters for "?" placeholder - * resolution, for the current batch entry. - *

The given list will be added to existing positional parameters - * for the current batch entry, if any. Each element from the complete - * list will be bound as a JDBC positional parameter with a corresponding - * JDBC index (i.e. list index + 1). - * @param values the parameter values to bind - * @return this batch specification (for chaining) - * @see StatementSpec#params(Object...) - */ - BatchSpec params(Object... values); - - /** - * Bind a list of positional parameters for "?" placeholder resolution, - * for the current batch entry. - *

The given list will be added to existing positional parameters - * for the current batch entry, if any. Each element from the complete - * list will be bound as a JDBC positional parameter with a corresponding - * JDBC index (i.e. list index + 1). + * Define a batch entry with the given list of positional parameters for + * "?" placeholder resolution. + *

Each element of the list will be bound as a JDBC positional parameter + * with a corresponding JDBC index (i.e. list index + 1). + *

Note that the given list represents the parameters of a single + * entry; see {@link #entries(List)} for defining several entries at once. * @param values the parameter values to bind * @return this batch specification (for chaining) + * @throws IllegalStateException if the list is empty, or if named and + * indexed parameters are mixed within the batch * @see StatementSpec#params(List) */ - BatchSpec params(List values); + BatchSpec entry(List values); /** - * Bind named statement parameters for ":x" placeholder resolution, - * for the current batch entry. - *

The given map will be merged into existing named parameters - * for the current batch entry, if any. + * Define a batch entry with the given named parameters for ":x" + * placeholder resolution. * @param paramMap a map of names and parameter values to bind * @return this batch specification (for chaining) + * @throws IllegalStateException if the map is empty, or if named and + * indexed parameters are mixed within the batch * @see StatementSpec#params(Map) */ - BatchSpec params(Map paramMap); + BatchSpec entry(Map paramMap); /** - * Bind named statement parameters for ":x" placeholder resolution, - * for the current batch entry. - *

The given parameter object will define all named parameters for - * the current batch entry, based on its JavaBean properties, record - * components, or raw fields. A Map instance can be provided as a - * complete parameter source as well. - * @param namedParamObject a custom parameter object + * Define several batch entries at once, with each given parameter object + * defining the named parameters for one entry. + *

Each parameter object is resolved in the same fashion as for + * {@link BatchEntry#paramSource(Object)}: based on its JavaBean properties, + * record components, or raw fields. A {@link Map} or a + * {@link SqlParameterSource} instance is supported as well. + * @param namedParamObjects the parameter objects, one per batch entry * @return this batch specification (for chaining) - * @see StatementSpec#paramSource(Object) + * @throws IllegalStateException if named and indexed parameters are + * mixed within the batch + * @see #entries(List) */ - BatchSpec paramSource(Object namedParamObject); + BatchSpec entries(Object... namedParamObjects); /** - * Bind named statement parameters for ":x" placeholder resolution, - * for the current batch entry. - *

The given parameter source will define all named parameters for - * the current batch entry, possibly associating specific SQL types - * with each value. - * @param namedParamSource a custom {@link SqlParameterSource} instance + * Define several batch entries at once, with each element of the given + * list defining the named parameters for one entry. + *

Each parameter object is resolved in the same fashion as for + * {@link BatchEntry#paramSource(Object)}: based on its JavaBean properties, + * record components, or raw fields. A {@link Map} or a + * {@link SqlParameterSource} instance is supported as well. + *

Note that each element of the given list represents a separate + * entry; see {@link #entry(List)} for defining a single entry with + * positional parameters. + * @param namedParamObjects the parameter objects, one per batch entry * @return this batch specification (for chaining) - * @see StatementSpec#paramSource(SqlParameterSource) + * @throws IllegalStateException if named and indexed parameters are + * mixed within the batch */ - BatchSpec paramSource(SqlParameterSource namedParamSource); + BatchSpec entries(List namedParamObjects); /** - * Complete the current set of parameters as one batch entry and start a - * new one for the subsequent parameter bindings. - * @return this batch specification (for chaining) - */ - BatchSpec add(); - - /** - * Execute the accumulated sets of parameters as a batch update, completing - * the current (final) set as the last batch entry. + * Execute the accumulated batch entries as a batch update. * @return an array containing the numbers of rows affected by each execution in the batch * (may also contain special JDBC-defined negative values for affected rows such as * {@link java.sql.Statement#SUCCESS_NO_INFO}/{@link java.sql.Statement#EXECUTE_FAILED}) @@ -527,8 +476,8 @@ public interface JdbcClient { int[] update(); /** - * Execute the accumulated sets of parameters as a batch update, completing - * the current (final) set as the last batch entry, returning generated keys. + * Execute the accumulated batch entries as a batch update, returning + * generated keys. * @param generatedKeyHolder a {@link KeyHolder} that will hold the generated keys * @return an array containing the numbers of rows affected by each execution in the batch * (may also contain special JDBC-defined negative values for affected rows such as @@ -541,8 +490,8 @@ public interface JdbcClient { int[] update(KeyHolder generatedKeyHolder); /** - * Execute the accumulated sets of parameters as a batch update, completing - * the current (final) set as the last batch entry, returning generated keys. + * Execute the accumulated batch entries as a batch update, returning + * generated keys. * @param generatedKeyHolder a {@link KeyHolder} that will hold the generated keys * @param keyColumnNames names of the columns that will have keys generated for them * @return an array containing the numbers of rows affected by each execution in the batch @@ -557,6 +506,97 @@ public interface JdbcClient { } + /** + * A specification for binding the parameters of a single batch entry, + * as provided to {@link BatchSpec#entry(Consumer)}. + * + *

Parameters are bound in the same fashion as for a single {@link StatementSpec}, + * either as JDBC-style positional parameters or as Spring-style named parameters + * (but not both). + * + * @since 7.1 + * @see BatchSpec#entry(Consumer) + */ + interface BatchEntry { + + /** + * Bind a positional JDBC statement parameter for "?" placeholder resolution + * by implicit order of parameter value registration. + *

This is primarily intended for statements with a single parameter + * or very few parameters, registering each parameter value in the order + * of the parameter's occurrence in the SQL statement. + * @param value the parameter value to bind + * @return this batch entry (for chaining) + * @see StatementSpec#param(Object) + */ + BatchEntry param(@Nullable Object value); + + /** + * Bind a positional JDBC statement parameter for "?" placeholder resolution + * by explicit JDBC statement parameter index. + * @param jdbcIndex the JDBC-style index (starting with 1) + * @param value the parameter value to bind + * @return this batch entry (for chaining) + * @see StatementSpec#param(int, Object) + */ + BatchEntry param(int jdbcIndex, @Nullable Object value); + + /** + * Bind a positional JDBC statement parameter for "?" placeholder resolution + * by explicit JDBC statement parameter index. + * @param jdbcIndex the JDBC-style index (starting with 1) + * @param value the parameter value to bind + * @param sqlType the associated SQL type (see {@link java.sql.Types}) + * @return this batch entry (for chaining) + * @see StatementSpec#param(int, Object, int) + */ + BatchEntry param(int jdbcIndex, @Nullable Object value, int sqlType); + + /** + * Bind a named statement parameter for ":x" placeholder resolution, + * with each "x" name matching a ":x" placeholder in the SQL statement. + * @param name the parameter name + * @param value the parameter value to bind + * @return this batch entry (for chaining) + * @see StatementSpec#param(String, Object) + */ + BatchEntry param(String name, @Nullable Object value); + + /** + * Bind a named statement parameter for ":x" placeholder resolution, + * with each "x" name matching a ":x" placeholder in the SQL statement. + * @param name the parameter name + * @param value the parameter value to bind + * @param sqlType the associated SQL type (see {@link java.sql.Types}) + * @return this batch entry (for chaining) + * @see StatementSpec#param(String, Object, int) + */ + BatchEntry param(String name, @Nullable Object value, int sqlType); + + /** + * Bind named statement parameters for ":x" placeholder resolution. + *

The given parameter object will define all named parameters for + * this entry, based on its JavaBean properties, record components, or + * raw fields. A Map instance can be provided as a complete parameter + * source as well. + * @param namedParamObject a custom parameter object + * @return this batch entry (for chaining) + * @see StatementSpec#paramSource(Object) + */ + BatchEntry paramSource(Object namedParamObject); + + /** + * Bind named statement parameters for ":x" placeholder resolution. + *

The given parameter source will define all named parameters for + * this entry, possibly associating specific SQL types with each value. + * @param namedParamSource a custom {@link SqlParameterSource} instance + * @return this batch entry (for chaining) + * @see StatementSpec#paramSource(SqlParameterSource) + */ + BatchEntry paramSource(SqlParameterSource namedParamSource); + } + + /** * A specification for simple result queries. */ diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIndexedParameterTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIndexedParameterTests.java index 39492f93af3..ce92d12290c 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIndexedParameterTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIndexedParameterTests.java @@ -340,7 +340,7 @@ class JdbcClientIndexedParameterTests { } int[] rowsAffected = client.sql(UPDATE_INDEXED_PARAMETERS).batch() - .param(1, 1).param(2, 1).add() + .entry(entry -> entry.param(1, 1).param(2, 1)) .update(); assertThat(rowsAffected).containsExactly(1); @@ -367,8 +367,8 @@ class JdbcClientIndexedParameterTests { } int[] rowsAffected = client.sql(UPDATE_INDEXED_PARAMETERS).batch() - .param(1, 1).param(2, 1).add() - .param(1, 2).param(2, 2).add() + .entry(entry -> entry.param(1, 1).param(2, 1)) + .entry(entry -> entry.param(1, 2).param(2, 2)) .update(); assertThat(rowsAffected).containsExactly(1, 1); @@ -416,12 +416,8 @@ class JdbcClientIndexedParameterTests { } int[] rowsAffected = client.sql(UPDATE_INDEXED_PARAMETERS).batch() - .param(1, new SqlParameterValue(Types.DECIMAL, 1)) - .param(2, new SqlParameterValue(Types.INTEGER, 1)) - .add() - .param(1, new SqlParameterValue(Types.DECIMAL, 2)) - .param(2, new SqlParameterValue(Types.INTEGER, 2)) - .add() + .entry(entry -> entry.param(1, new SqlParameterValue(Types.DECIMAL, 1)).param(2, new SqlParameterValue(Types.INTEGER, 1))) + .entry(entry -> entry.param(1, new SqlParameterValue(Types.DECIMAL, 2)).param(2, new SqlParameterValue(Types.INTEGER, 2))) .update(); assertThat(rowsAffected).containsExactly(1, 1); @@ -453,12 +449,8 @@ class JdbcClientIndexedParameterTests { } int[] rowsAffected = client.sql(UPDATE_INDEXED_PARAMETERS).batch() - .param(1, 1, Types.DECIMAL) - .param(2, 1, Types.INTEGER) - .add() - .param(1, 2, Types.DECIMAL) - .param(2, 2, Types.INTEGER) - .add() + .entry(entry -> entry.param(1, 1, Types.DECIMAL).param(2, 1, Types.INTEGER)) + .entry(entry -> entry.param(1, 2, Types.DECIMAL).param(2, 2, Types.INTEGER)) .update(); assertThat(rowsAffected).containsExactly(1, 1); @@ -523,8 +515,8 @@ class JdbcClientIndexedParameterTests { KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); int[] rowsAffected = client.sql(INSERT_GENERATE_KEYS).batch() - .param("rod").add() - .param("johnson").add() + .entry(entry -> entry.param("rod")) + .entry(entry -> entry.param("johnson")) .update(generatedKeyHolder); assertThat(rowsAffected).containsExactly(1, 1); @@ -592,8 +584,8 @@ class JdbcClientIndexedParameterTests { KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); int[] rowsAffected = client.sql(INSERT_GENERATE_KEYS).batch() - .param("rod").add() - .param("johnson").add() + .entry(entry -> entry.param("rod")) + .entry(entry -> entry.param("johnson")) .update(generatedKeyHolder, "id"); assertThat(rowsAffected).containsExactly(1, 1); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java index 83683944a34..d18e06410cf 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java @@ -56,6 +56,9 @@ class JdbcClientIntegrationTests { private static final String INSERT_WITH_NAMED_PARAMS = "INSERT INTO users (first_name, last_name) VALUES(:firstName, :lastName)"; + private static final String MIXED_BATCH_ENTRIES_MESSAGE = + "Configure either named or indexed parameters for all batch entries, not both"; + private final EmbeddedDatabase embeddedDatabase = new EmbeddedDatabaseBuilder(new ClassRelativeResourceLoader(DatabasePopulator.class)) @@ -153,67 +156,11 @@ class JdbcClientIntegrationTests { } @Test - void batchUpdateWithIndexedParameters() { + void batchUpdateWithIndexedParameterList() { int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS) .batch() - .params("Jane", "Smith").add() - .params("John", "Doe") - .update(); - - assertThat(rowsAffected).containsExactly(1, 1); - assertNumUsers(3); - assertUser(1, "Jane", "Smith"); - assertUser(2, "John", "Doe"); - } - - @Test - void batchUpdateWithJdbcIndexParameters() { - int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS) - .batch() - .param(2, "Smith").param(1, "Jane").add() - .param(2, "Doe").param(1, "John") - .update(); - - assertThat(rowsAffected).containsExactly(1, 1); - assertNumUsers(3); - assertUser(1, "Jane", "Smith"); - assertUser(2, "John", "Doe"); - } - - @Test - void batchUpdateWithJdbcIndexParametersAndSqlType() { - int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS) - .batch() - .param(2, "Smith", Types.VARCHAR).param(1, "Jane", Types.VARCHAR).add() - .param(2, "Doe", Types.VARCHAR).param(1, "John", Types.VARCHAR) - .update(); - - assertThat(rowsAffected).containsExactly(1, 1); - assertNumUsers(3); - assertUser(1, "Jane", "Smith"); - assertUser(2, "John", "Doe"); - } - - @Test - void batchUpdateWithNamedParameters() { - int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) - .batch() - .param("firstName", "Jane").param("lastName", "Smith").add() - .param("firstName", "John").param("lastName", "Doe") - .update(); - - assertThat(rowsAffected).containsExactly(1, 1); - assertNumUsers(3); - assertUser(1, "Jane", "Smith"); - assertUser(2, "John", "Doe"); - } - - @Test - void batchUpdateWithNamedParametersAndSqlType() { - int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) - .batch() - .param("firstName", "Jane", Types.VARCHAR).param("lastName", "Smith", Types.VARCHAR).add() - .param("firstName", "John", Types.VARCHAR).param("lastName", "Doe", Types.VARCHAR) + .entry(List.of("Jane", "Smith")) + .entry(List.of("John", "Doe")) .update(); assertThat(rowsAffected).containsExactly(1, 1); @@ -226,8 +173,8 @@ class JdbcClientIntegrationTests { void batchUpdateWithIndividualIndexedParameters() { int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS) .batch() - .param("Jane").param("Smith").add() - .param("John").param("Doe") + .entry(entry -> entry.param("Jane").param("Smith")) + .entry(entry -> entry.param("John").param("Doe")) .update(); assertThat(rowsAffected).containsExactly(1, 1); @@ -237,11 +184,53 @@ class JdbcClientIntegrationTests { } @Test - void batchUpdateWithIndexedParameterList() { + void batchUpdateWithJdbcIndexParameters() { int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS) .batch() - .params(List.of("Jane", "Smith")).add() - .params(List.of("John", "Doe")) + .entry(entry -> entry.param(2, "Smith").param(1, "Jane")) + .entry(entry -> entry.param(2, "Doe").param(1, "John")) + .update(); + + assertThat(rowsAffected).containsExactly(1, 1); + assertNumUsers(3); + assertUser(1, "Jane", "Smith"); + assertUser(2, "John", "Doe"); + } + + @Test + void batchUpdateWithJdbcIndexParametersAndSqlType() { + int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS) + .batch() + .entry(entry -> entry.param(2, "Smith", Types.VARCHAR).param(1, "Jane", Types.VARCHAR)) + .entry(entry -> entry.param(2, "Doe", Types.VARCHAR).param(1, "John", Types.VARCHAR)) + .update(); + + assertThat(rowsAffected).containsExactly(1, 1); + assertNumUsers(3); + assertUser(1, "Jane", "Smith"); + assertUser(2, "John", "Doe"); + } + + @Test + void batchUpdateWithNamedParameters() { + int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) + .batch() + .entry(entry -> entry.param("firstName", "Jane").param("lastName", "Smith")) + .entry(entry -> entry.param("firstName", "John").param("lastName", "Doe")) + .update(); + + assertThat(rowsAffected).containsExactly(1, 1); + assertNumUsers(3); + assertUser(1, "Jane", "Smith"); + assertUser(2, "John", "Doe"); + } + + @Test + void batchUpdateWithNamedParametersAndSqlType() { + int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) + .batch() + .entry(entry -> entry.param("firstName", "Jane", Types.VARCHAR).param("lastName", "Smith", Types.VARCHAR)) + .entry(entry -> entry.param("firstName", "John", Types.VARCHAR).param("lastName", "Doe", Types.VARCHAR)) .update(); assertThat(rowsAffected).containsExactly(1, 1); @@ -254,8 +243,8 @@ class JdbcClientIntegrationTests { void batchUpdateWithNamedParameterMap() { int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) .batch() - .params(Map.of("firstName", "Jane", "lastName", "Smith")).add() - .params(Map.of("firstName", "John", "lastName", "Doe")) + .entry(Map.of("firstName", "Jane", "lastName", "Smith")) + .entry(Map.of("firstName", "John", "lastName", "Doe")) .update(); assertThat(rowsAffected).containsExactly(1, 1); @@ -265,11 +254,11 @@ class JdbcClientIntegrationTests { } @Test - void batchUpdateWithParameterObjects() { + void batchUpdateWithParameterObject() { int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) .batch() - .paramSource(new NewUser("Jane", "Smith")).add() - .paramSource(new NewUser("John", "Doe")) + .entry(entry -> entry.paramSource(new NewUser("Jane", "Smith"))) + .entry(entry -> entry.paramSource(new NewUser("John", "Doe"))) .update(); assertThat(rowsAffected).containsExactly(1, 1); @@ -279,11 +268,11 @@ class JdbcClientIntegrationTests { } @Test - void batchUpdateWithParameterSources() { + void batchUpdateWithParameterSource() { int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) .batch() - .paramSource(new MapSqlParameterSource("firstName", "Jane").addValue("lastName", "Smith")).add() - .paramSource(new MapSqlParameterSource("firstName", "John").addValue("lastName", "Doe")) + .entry(entry -> entry.paramSource(new MapSqlParameterSource("firstName", "Jane").addValue("lastName", "Smith"))) + .entry(entry -> entry.paramSource(new MapSqlParameterSource("firstName", "John").addValue("lastName", "Doe"))) .update(); assertThat(rowsAffected).containsExactly(1, 1); @@ -293,11 +282,24 @@ class JdbcClientIntegrationTests { } @Test - void batchUpdateWithTrailingAdd() { + void batchUpdateWithListOfParameterObjects() { + List users = List.of(new NewUser("Jane", "Smith"), new NewUser("John", "Doe")); + + int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS).batch().entries(users).update(); + + assertThat(rowsAffected).containsExactly(1, 1); + assertNumUsers(3); + assertUser(1, "Jane", "Smith"); + assertUser(2, "John", "Doe"); + } + + @Test + void batchUpdateWithVarargsParameterMaps() { int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) .batch() - .param("firstName", "Jane").param("lastName", "Smith").add() - .param("firstName", "John").param("lastName", "Doe").add() + .entries( + Map.of("firstName", "Jane", "lastName", "Smith"), + Map.of("firstName", "John", "lastName", "Doe")) .update(); assertThat(rowsAffected).containsExactly(1, 1); @@ -306,6 +308,38 @@ class JdbcClientIntegrationTests { assertUser(2, "John", "Doe"); } + @Test + void batchUpdateWithVarargsParameterSources() { + int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) + .batch() + .entries( + new MapSqlParameterSource("firstName", "Jane").addValue("lastName", "Smith"), + new MapSqlParameterSource("firstName", "John").addValue("lastName", "Doe")) + .update(); + + assertThat(rowsAffected).containsExactly(1, 1); + assertNumUsers(3); + assertUser(1, "Jane", "Smith"); + assertUser(2, "John", "Doe"); + } + + @Test + void batchUpdateWithCombinedEntries() { + int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) + .batch() + .entries(List.of(new NewUser("Jane", "Smith"), new NewUser("John", "Doe"))) + .entry(entry -> entry.param("firstName", "Jack").param("lastName", "Jones")) + .entry(Map.of("firstName", "Jill", "lastName", "Brown")) + .update(); + + assertThat(rowsAffected).containsExactly(1, 1, 1, 1); + assertNumUsers(5); + assertUser(1, "Jane", "Smith"); + assertUser(2, "John", "Doe"); + assertUser(3, "Jack", "Jones"); + assertUser(4, "Jill", "Brown"); + } + @Test void emptyBatchUpdate() { int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS).batch().update(); @@ -315,32 +349,82 @@ class JdbcClientIntegrationTests { } @Test - void batchUpdateRejectsMixedParametersWithinEntry() { - assertThatIllegalStateException().isThrownBy(() -> - this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS) - .batch() - .param("Jane").param("lastName", "Smith") - .update()); + void emptyEntriesBatchUpdate() { + int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS).batch().entries(List.of()).update(); + + assertThat(rowsAffected).isEmpty(); + assertNumUsers(1); } @Test - void batchUpdateRejectsMixedParametersAcrossEntries() { - assertThatIllegalStateException().isThrownBy(() -> - this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) - .batch() - .param("firstName", "Jane").param("lastName", "Smith").add() - .params("John", "Doe") - .update()); + void batchUpdateRejectsEntryWithoutParameters() { + String message = "Configure at least one parameter for each batch entry"; + + assertThatIllegalStateException() + .isThrownBy(() -> this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS).batch().entry(entry -> {})) + .withMessage(message); + assertThatIllegalStateException() + .isThrownBy(() -> this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).batch().entry(List.of())) + .withMessage(message); + assertThatIllegalStateException() + .isThrownBy(() -> this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS).batch().entry(Map.of())) + .withMessage(message); + } + + @Test + void batchUpdateRejectsMixedParametersWithinEntry() { + assertThatIllegalStateException() + .isThrownBy(() -> this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS) + .batch().entry(entry -> entry.param("Jane").param("lastName", "Smith"))) + .withMessage("Configure either named or indexed parameters, not both"); + } + + @Test + void batchUpdateRejectsIndexedParametersWithParameterSourceWithinEntry() { + assertThatIllegalStateException() + .isThrownBy(() -> this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS) + .batch().entry(entry -> entry.param("Jane").paramSource(new NewUser("Jane", "Smith")))) + .withMessage("Configure either named or indexed parameters, not both"); } @Test void batchUpdateRejectsIndividualNamedParametersWithParameterSource() { - assertThatIllegalStateException().isThrownBy(() -> - this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) + assertThatIllegalStateException() + .isThrownBy(() -> this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) .batch() - .param("firstName", "Jane") - .paramSource(new MapSqlParameterSource("lastName", "Smith")) - .update()); + .entry(entry -> entry.param("firstName", "Jane") + .paramSource(new MapSqlParameterSource("lastName", "Smith")))) + .withMessage("Configure either individual named parameters or a SqlParameterSource, not both"); + } + + @Test + void batchUpdateRejectsIndexedEntryAfterNamedEntry() { + assertThatIllegalStateException() + .isThrownBy(() -> this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) + .batch() + .entry(Map.of("firstName", "Jane", "lastName", "Smith")) + .entry(List.of("John", "Doe"))) + .withMessage(MIXED_BATCH_ENTRIES_MESSAGE); + } + + @Test + void batchUpdateRejectsNamedEntryAfterIndexedEntry() { + assertThatIllegalStateException() + .isThrownBy(() -> this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS) + .batch() + .entry(List.of("Jane", "Smith")) + .entry(Map.of("firstName", "John", "lastName", "Doe"))) + .withMessage(MIXED_BATCH_ENTRIES_MESSAGE); + } + + @Test + void batchUpdateRejectsEntriesAfterIndexedEntry() { + assertThatIllegalStateException() + .isThrownBy(() -> this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS) + .batch() + .entry(List.of("Jane", "Smith")) + .entries(new NewUser("John", "Doe"))) + .withMessage(MIXED_BATCH_ENTRIES_MESSAGE); } @Test @@ -348,8 +432,8 @@ class JdbcClientIntegrationTests { KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) .batch() - .param("firstName", "Jane").param("lastName", "Smith").add() - .param("firstName", "John").param("lastName", "Doe") + .entry(Map.of("firstName", "Jane", "lastName", "Smith")) + .entry(Map.of("firstName", "John", "lastName", "Doe")) .update(generatedKeyHolder); assertThat(rowsAffected).containsExactly(1, 1); @@ -364,8 +448,8 @@ class JdbcClientIntegrationTests { KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS) .batch() - .params("Jane", "Smith").add() - .params("John", "Doe") + .entry(List.of("Jane", "Smith")) + .entry(List.of("John", "Doe")) .update(generatedKeyHolder); assertThat(rowsAffected).containsExactly(1, 1); @@ -380,8 +464,8 @@ class JdbcClientIntegrationTests { KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) .batch() - .param("firstName", "Jane").param("lastName", "Smith").add() - .param("firstName", "John").param("lastName", "Doe") + .entry(entry -> entry.param("firstName", "Jane").param("lastName", "Smith")) + .entry(entry -> entry.param("firstName", "John").param("lastName", "Doe")) .update(generatedKeyHolder, "id"); assertThat(rowsAffected).containsExactly(1, 1); @@ -391,13 +475,27 @@ class JdbcClientIntegrationTests { assertThat(generatedKeyHolder.getKeyList()).containsExactly(Map.of("ID", 1), Map.of("ID", 2)); } + @Test + void batchUpdateWithParameterObjectsAndGeneratedKeys() { + KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); + int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_NAMED_PARAMS) + .batch().entries(new NewUser("Jane", "Smith"), new NewUser("John", "Doe")) + .update(generatedKeyHolder); + + assertThat(rowsAffected).containsExactly(1, 1); + assertNumUsers(3); + assertUser(1, "Jane", "Smith"); + assertUser(2, "John", "Doe"); + assertThat(generatedKeyHolder.getKeyList()).containsExactly(Map.of("ID", 1), Map.of("ID", 2)); + } + @Test void batchUpdateWithIndexedParametersAndGeneratedKeysAndKeyColumnNames() { KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS) .batch() - .params("Jane", "Smith").add() - .params("John", "Doe") + .entry(entry -> entry.param("Jane").param("Smith")) + .entry(entry -> entry.param("John").param("Doe")) .update(generatedKeyHolder, "id"); assertThat(rowsAffected).containsExactly(1, 1); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientNamedParameterTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientNamedParameterTests.java index 678e2d21859..44e0bfd9aa7 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientNamedParameterTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientNamedParameterTests.java @@ -406,7 +406,7 @@ class JdbcClientNamedParameterTests { } int[] rowsAffected = client.sql(UPDATE_NAMED_PARAMETERS).batch() - .param("perfId", 1).param("priceId", 1).add() + .entry(entry -> entry.param("perfId", 1).param("priceId", 1)) .update(); assertThat(rowsAffected).containsExactly(1); @@ -433,8 +433,8 @@ class JdbcClientNamedParameterTests { } int[] rowsAffected = client.sql(UPDATE_NAMED_PARAMETERS).batch() - .param("perfId", 1).param("priceId", 1).add() - .param("perfId", 2).param("priceId", 2).add() + .entry(entry -> entry.param("perfId", 1).param("priceId", 1)) + .entry(entry -> entry.param("perfId", 2).param("priceId", 2)) .update(); assertThat(rowsAffected).containsExactly(1, 1); @@ -482,12 +482,8 @@ class JdbcClientNamedParameterTests { } int[] rowsAffected = client.sql(UPDATE_NAMED_PARAMETERS).batch() - .param("perfId", new SqlParameterValue(Types.DECIMAL, 1)) - .param("priceId", new SqlParameterValue(Types.INTEGER, 1)) - .add() - .param("perfId", new SqlParameterValue(Types.DECIMAL, 2)) - .param("priceId", new SqlParameterValue(Types.INTEGER, 2)) - .add() + .entry(entry -> entry.param("perfId", new SqlParameterValue(Types.DECIMAL, 1)).param("priceId", new SqlParameterValue(Types.INTEGER, 1))) + .entry(entry -> entry.param("perfId", new SqlParameterValue(Types.DECIMAL, 2)).param("priceId", new SqlParameterValue(Types.INTEGER, 2))) .update(); assertThat(rowsAffected).containsExactly(1, 1); @@ -519,12 +515,8 @@ class JdbcClientNamedParameterTests { } int[] rowsAffected = client.sql(UPDATE_NAMED_PARAMETERS).batch() - .param("perfId", 1, Types.DECIMAL) - .param("priceId", 1, Types.INTEGER) - .add() - .param("perfId", 2, Types.DECIMAL) - .param("priceId", 2, Types.INTEGER) - .add() + .entry(entry -> entry.param("perfId", 1, Types.DECIMAL).param("priceId", 1, Types.INTEGER)) + .entry(entry -> entry.param("perfId", 2, Types.DECIMAL).param("priceId", 2, Types.INTEGER)) .update(); assertThat(rowsAffected).containsExactly(1, 1); @@ -589,8 +581,8 @@ class JdbcClientNamedParameterTests { KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); int[] rowsAffected = client.sql(INSERT_GENERATE_KEYS).batch() - .param("name", "rod").add() - .param("name", "johnson").add() + .entry(entry -> entry.param("name", "rod")) + .entry(entry -> entry.param("name", "johnson")) .update(generatedKeyHolder); assertThat(rowsAffected).containsExactly(1, 1); @@ -658,8 +650,8 @@ class JdbcClientNamedParameterTests { KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); int[] rowsAffected = client.sql(INSERT_GENERATE_KEYS).batch() - .param("name", "rod").add() - .param("name", "johnson").add() + .entry(entry -> entry.param("name", "rod")) + .entry(entry -> entry.param("name", "johnson")) .update(generatedKeyHolder, "id"); assertThat(rowsAffected).containsExactly(1, 1);