mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-10-07 07:29:59 +00:00
Introduce entry-based DSL for JdbcClient batch updates
Prior to this commit, JdbcClient's BatchSpec bound parameters to a "current" batch entry and separated consecutive entries via add(). That style was error-prone, since a missing add() still compiled but silently merged parameters into a single entry. It also did not lend itself to providing batch parameters dynamically — for example, from an existing collection of domain objects. To address that, this commit replaces the chaining style with self-contained batch entries. BatchSpec now provides entry(...) methods that define a single entry — via a Consumer<BatchEntry> callback, a List of positional parameters, or a Map of named parameters — as well as entries(...) methods that define one entry per parameter object. Such calls can be freely combined within the same batch. The new BatchEntry type exposes only the singular param(...) and paramSource(...) methods. The add() method as well as the param(...), params(...), and paramSource(...) methods have been removed from BatchSpec. Closes gh-37341
This commit is contained in:
@@ -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<Actor> actors = ...;
|
||||
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
|
||||
.batch()
|
||||
.entries(actors)
|
||||
.update();
|
||||
----
|
||||
|
||||
|
||||
+109
-76
@@ -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<BatchEntry> 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<String, ?> 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<String, ?> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
* <p>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()}:
|
||||
* <p>Define each batch entry through the returned {@link BatchSpec} and
|
||||
* finally trigger execution through {@link BatchSpec#update()}:
|
||||
* <pre class="code">
|
||||
* 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();
|
||||
* </pre>
|
||||
* <p>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
|
||||
* <p>Alternatively, provide an entire list of parameter objects at once,
|
||||
* with each object representing one batch entry:
|
||||
* <pre class="code">
|
||||
* List<User> users = ...;
|
||||
* int[] rowsAffected = client.sql("INSERT INTO user (first_name, last_name) VALUES (:firstName, :lastName)")
|
||||
* .batch()
|
||||
* .entries(users)
|
||||
* .update();
|
||||
* </pre>
|
||||
* @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.
|
||||
*
|
||||
* <p>Parameters are bound in the same fashion as for a single {@link StatementSpec},
|
||||
* <p>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.
|
||||
* <p>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.
|
||||
* <p>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<BatchEntry> 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.
|
||||
* <p>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.
|
||||
* <p>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.
|
||||
* <p>Each element of the list will be bound as a JDBC positional parameter
|
||||
* with a corresponding JDBC index (i.e. list index + 1).
|
||||
* <p>Note that the given list represents the parameters of a <em>single</em>
|
||||
* 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.
|
||||
* <p>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<String, ?> paramMap);
|
||||
BatchSpec entry(Map<String, ?> paramMap);
|
||||
|
||||
/**
|
||||
* Bind named statement parameters for ":x" placeholder resolution,
|
||||
* for the current batch entry.
|
||||
* <p>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.
|
||||
* <p>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.
|
||||
* <p>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.
|
||||
* <p>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.
|
||||
* <p>Note that each element of the given list represents a <em>separate</em>
|
||||
* 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)}.
|
||||
*
|
||||
* <p>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.
|
||||
* <p>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.
|
||||
* <p>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.
|
||||
* <p>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.
|
||||
*/
|
||||
|
||||
+11
-19
@@ -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);
|
||||
|
||||
+199
-101
@@ -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<NewUser> 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);
|
||||
|
||||
+11
-19
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user