Improve JdbcClient to align BatchSpec with StatementSpec

This commit introduces the following methods in BatchSpec:

1. `param(int jdbcIndex, @Nullable Object value)`
2. `param(int jdbcIndex, @Nullable Object value, int sqlType)`
3. `param(String name, @Nullable Object value, int sqlType)`

Closes gh-37307

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
This commit is contained in:
Yanming Zhou
2026-09-28 11:51:46 +02:00
committed by GitHub
parent 30d025e57e
commit 1d21c6a578
5 changed files with 552 additions and 96 deletions
@@ -161,32 +161,10 @@ final class DefaultJdbcClient implements JdbcClient {
@Override
public StatementSpec param(int jdbcIndex, @Nullable Object value) {
if (jdbcIndex < 1) {
throw new IllegalArgumentException("Invalid JDBC index: needs to start at 1");
}
validateIndexedParamValue(value);
int index = jdbcIndex - 1;
int size = this.indexedParams.size();
if (index < size) {
this.indexedParams.set(index, value);
}
else {
for (int i = size; i < index; i++) {
this.indexedParams.add(null);
}
this.indexedParams.add(value);
}
addIndexedParam(this.indexedParams, jdbcIndex, value);
return this;
}
private void validateIndexedParamValue(@Nullable Object value) {
if (value instanceof Iterable) {
throw new IllegalArgumentException("Invalid positional parameter value of type Iterable (" +
value.getClass().getSimpleName() +
"): Parameter expansion is only supported with named parameters.");
}
}
@Override
public StatementSpec param(int jdbcIndex, @Nullable Object value, int sqlType) {
return param(jdbcIndex, new SqlParameterValue(sqlType, value));
@@ -333,6 +311,32 @@ final class DefaultJdbcClient implements JdbcClient {
return pscf.newPreparedStatementCreator(this.indexedParams);
}
private static void addIndexedParam(List<@Nullable Object> indexedParams, int jdbcIndex, @Nullable Object value) {
if (jdbcIndex < 1) {
throw new IllegalArgumentException("Invalid JDBC index: needs to start at 1");
}
validateIndexedParamValue(value);
int index = jdbcIndex - 1;
int size = indexedParams.size();
if (index < size) {
indexedParams.set(index, value);
}
else {
for (int i = size; i < index; i++) {
indexedParams.add(null);
}
indexedParams.add(value);
}
}
private static void validateIndexedParamValue(@Nullable Object value) {
if (value instanceof Iterable) {
throw new IllegalArgumentException("Invalid positional parameter value of type Iterable (" +
value.getClass().getSimpleName() +
"): Parameter expansion is only supported with named parameters.");
}
}
private class DefaultBatchSpec implements BatchSpec {
@@ -355,12 +359,29 @@ final class DefaultJdbcClient implements JdbcClient {
return this;
}
@Override
public BatchSpec param(int jdbcIndex, @Nullable Object value) {
addIndexedParam(this.currentIndexedParams, jdbcIndex, value);
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);
return this;
}
@Override
public BatchSpec param(String name, @Nullable Object value, int sqlType) {
this.currentNamedParams.addValue(name, value, sqlType);
return this;
}
@Override
public BatchSpec params(Object... values) {
Collections.addAll(this.currentIndexedParams, values);
@@ -398,6 +398,27 @@ public interface JdbcClient {
*/
BatchSpec 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 specification (for chaining)
* @see java.sql.PreparedStatement#setObject(int, Object)
*/
BatchSpec 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 specification (for chaining)
* @see java.sql.PreparedStatement#setObject(int, Object, int)
*/
BatchSpec param(int jdbcIndex, @Nullable Object value, int sqlType);
/**
* Bind a named parameter for the current batch entry.
* @param name the parameter name
@@ -407,6 +428,16 @@ public interface JdbcClient {
*/
BatchSpec param(String name, @Nullable Object value);
/**
* Bind a named parameter 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 java.sql.PreparedStatement#setObject(int, Object, int)
*/
BatchSpec param(String name, @Nullable Object value, int sqlType);
/**
* Bind a var-args list of positional parameters for the current batch entry.
* @param values the parameter values to bind
@@ -25,6 +25,7 @@ import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
@@ -33,6 +34,9 @@ import javax.sql.DataSource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InOrder;
import org.springframework.jdbc.Customer;
import org.springframework.jdbc.core.SqlParameterValue;
@@ -42,6 +46,7 @@ import org.springframework.jdbc.support.KeyHolder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -67,21 +72,21 @@ class JdbcClientIndexedParameterTests {
private static final String[] COLUMN_NAMES = new String[] {"id", "forename"};
private Connection connection = mock();
private final Connection connection = mock();
private DataSource dataSource = mock();
private final DataSource dataSource = mock();
private PreparedStatement preparedStatement = mock();
private final PreparedStatement preparedStatement = mock();
private ResultSet resultSet = mock();
private final ResultSet resultSet = mock();
private ResultSetMetaData resultSetMetaData = mock();
private final ResultSetMetaData resultSetMetaData = mock();
private DatabaseMetaData databaseMetaData = mock();
private final DatabaseMetaData databaseMetaData = mock();
private JdbcClient client = JdbcClient.create(dataSource);
private final JdbcClient client = JdbcClient.create(dataSource);
private List<Object> params = new ArrayList<>();
private final List<Object> params = new ArrayList<>();
@BeforeEach
@@ -323,6 +328,66 @@ class JdbcClientIndexedParameterTests {
verify(connection).close();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void batchUpdateSingleRow(boolean supportsBatchUpdates) throws SQLException {
if (supportsBatchUpdates) {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(preparedStatement.executeBatch()).willReturn(new int[] {1});
}
else {
given(preparedStatement.executeUpdate()).willReturn(1);
}
int[] rowsAffected = client.sql(UPDATE_INDEXED_PARAMETERS).batch()
.param(1, 1).param(2, 1).add()
.update();
assertThat(rowsAffected).containsExactly(1);
InOrder inOrder = inOrder(connection, preparedStatement);
inOrder.verify(connection).prepareStatement(UPDATE_INDEXED_PARAMETERS);
inOrder.verify(preparedStatement).setObject(1, 1);
inOrder.verify(preparedStatement).setObject(2, 1);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).close();
inOrder.verify(connection).close();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void batchUpdateMultipleRows(boolean supportsBatchUpdates) throws SQLException {
if (supportsBatchUpdates) {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(preparedStatement.executeBatch()).willReturn(new int[] {1, 1});
}
else {
given(preparedStatement.executeUpdate()).willReturn(1);
}
int[] rowsAffected = client.sql(UPDATE_INDEXED_PARAMETERS).batch()
.param(1, 1).param(2, 1).add()
.param(1, 2).param(2, 2).add()
.update();
assertThat(rowsAffected).containsExactly(1, 1);
InOrder inOrder = inOrder(connection, preparedStatement);
inOrder.verify(connection).prepareStatement(UPDATE_INDEXED_PARAMETERS);
inOrder.verify(preparedStatement).setObject(1, 1);
inOrder.verify(preparedStatement).setObject(2, 1);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).setObject(1, 2);
inOrder.verify(preparedStatement).setObject(2, 2);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).close();
inOrder.verify(connection).close();
}
@Test
void updateWithTypedParameters() throws SQLException {
given(preparedStatement.executeUpdate()).willReturn(1);
@@ -339,6 +404,80 @@ class JdbcClientIndexedParameterTests {
verify(connection).close();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void batchUpdateWithTypedParameters(boolean supportsBatchUpdates) throws SQLException {
if (supportsBatchUpdates) {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(preparedStatement.executeBatch()).willReturn(new int[] {1, 1});
}
else {
given(preparedStatement.executeUpdate()).willReturn(1);
}
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()
.update();
assertThat(rowsAffected).containsExactly(1, 1);
InOrder inOrder = inOrder(connection, preparedStatement);
inOrder.verify(connection).prepareStatement(UPDATE_INDEXED_PARAMETERS);
inOrder.verify(preparedStatement).setObject(1, 1, Types.DECIMAL);
inOrder.verify(preparedStatement).setObject(2, 1, Types.INTEGER);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).setObject(1, 2, Types.DECIMAL);
inOrder.verify(preparedStatement).setObject(2, 2, Types.INTEGER);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).close();
inOrder.verify(connection).close();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void batchUpdateWithParametersAndSqlType(boolean supportsBatchUpdates) throws SQLException {
if (supportsBatchUpdates) {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(preparedStatement.executeBatch()).willReturn(new int[] {1, 1});
}
else {
given(preparedStatement.executeUpdate()).willReturn(1);
}
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()
.update();
assertThat(rowsAffected).containsExactly(1, 1);
InOrder inOrder = inOrder(connection, preparedStatement);
inOrder.verify(connection).prepareStatement(UPDATE_INDEXED_PARAMETERS);
inOrder.verify(preparedStatement).setObject(1, 1, Types.DECIMAL);
inOrder.verify(preparedStatement).setObject(2, 1, Types.INTEGER);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).setObject(1, 2, Types.DECIMAL);
inOrder.verify(preparedStatement).setObject(2, 2, Types.INTEGER);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).close();
inOrder.verify(connection).close();
}
@Test
void updateWithGeneratedKeys() throws SQLException {
given(resultSetMetaData.getColumnCount()).willReturn(1);
@@ -363,28 +502,49 @@ class JdbcClientIndexedParameterTests {
verify(connection).close();
}
@Test
void batchUpdateWithGeneratedKeys() throws SQLException {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void batchUpdateWithGeneratedKeys(boolean supportsBatchUpdates) throws SQLException {
if (supportsBatchUpdates) {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(preparedStatement.executeBatch()).willReturn(new int[] {1, 1});
}
else {
given(preparedStatement.executeUpdate()).willReturn(1);
}
given(resultSetMetaData.getColumnCount()).willReturn(1);
given(resultSetMetaData.getColumnLabel(1)).willReturn("1");
given(resultSetMetaData.getColumnLabel(1)).willReturn("id");
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getObject(1)).willReturn(11);
given(preparedStatement.executeUpdate()).willReturn(1);
given(resultSet.next()).willReturn(true, true, false);
given(resultSet.getObject(1)).willReturn(11, 12);
given(preparedStatement.getGeneratedKeys()).willReturn(resultSet);
given(connection.prepareStatement(INSERT_GENERATE_KEYS, PreparedStatement.RETURN_GENERATED_KEYS))
.willReturn(preparedStatement);
KeyHolder generatedKeyHolder = new GeneratedKeyHolder();
int[] rowsAffected = client.sql(INSERT_GENERATE_KEYS).batch().param("rod").add().update(generatedKeyHolder);
int[] rowsAffected = client.sql(INSERT_GENERATE_KEYS).batch()
.param("rod").add()
.param("johnson").add()
.update(generatedKeyHolder);
assertThat(rowsAffected).isEqualTo(new int[] { 1 });
assertThat(generatedKeyHolder.getKeyList()).hasSize(1);
assertThat(generatedKeyHolder.getKey()).isEqualTo(11);
verify(preparedStatement).setString(1, "rod");
verify(resultSet).close();
verify(preparedStatement).close();
verify(connection).close();
assertThat(rowsAffected).containsExactly(1, 1);
assertThat(generatedKeyHolder.getKeyList()).containsExactly(Map.of("id", 11), Map.of("id", 12));
InOrder inOrder = inOrder(connection, preparedStatement, resultSet);
inOrder.verify(connection).prepareStatement(INSERT_GENERATE_KEYS, PreparedStatement.RETURN_GENERATED_KEYS);
inOrder.verify(preparedStatement).setString(1, "rod");
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
else {
inOrder.verify(resultSet).close();
}
inOrder.verify(preparedStatement).setString(1, "johnson");
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(resultSet).close();
inOrder.verify(preparedStatement).close();
inOrder.verify(connection).close();
}
@Test
@@ -411,28 +571,49 @@ class JdbcClientIndexedParameterTests {
verify(connection).close();
}
@Test
void batchUpdateWithGeneratedKeysAndKeyColumnNames() throws SQLException {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void batchUpdateWithGeneratedKeysAndKeyColumnNames(boolean supportsBatchUpdates) throws SQLException {
if (supportsBatchUpdates) {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(preparedStatement.executeBatch()).willReturn(new int[] {1, 1});
}
else {
given(preparedStatement.executeUpdate()).willReturn(1);
}
given(resultSetMetaData.getColumnCount()).willReturn(1);
given(resultSetMetaData.getColumnLabel(1)).willReturn("1");
given(resultSetMetaData.getColumnLabel(1)).willReturn("id");
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getObject(1)).willReturn(11);
given(preparedStatement.executeUpdate()).willReturn(1);
given(resultSet.next()).willReturn(true, true, false);
given(resultSet.getObject(1)).willReturn(11, 12);
given(preparedStatement.getGeneratedKeys()).willReturn(resultSet);
given(connection.prepareStatement(INSERT_GENERATE_KEYS, new String[] {"id"}))
.willReturn(preparedStatement);
KeyHolder generatedKeyHolder = new GeneratedKeyHolder();
int[] rowsAffected = client.sql(INSERT_GENERATE_KEYS).batch().param("rod").add().update(generatedKeyHolder, "id");
int[] rowsAffected = client.sql(INSERT_GENERATE_KEYS).batch()
.param("rod").add()
.param("johnson").add()
.update(generatedKeyHolder, "id");
assertThat(rowsAffected).isEqualTo(new int[] { 1 });
assertThat(generatedKeyHolder.getKeyList()).hasSize(1);
assertThat(generatedKeyHolder.getKey()).isEqualTo(11);
verify(preparedStatement).setString(1, "rod");
verify(resultSet).close();
verify(preparedStatement).close();
verify(connection).close();
assertThat(rowsAffected).containsExactly(1, 1);
assertThat(generatedKeyHolder.getKeyList()).containsExactly(Map.of("id", 11), Map.of("id", 12));
InOrder inOrder = inOrder(connection, preparedStatement, resultSet);
inOrder.verify(connection).prepareStatement(INSERT_GENERATE_KEYS, new String[] {"id"});
inOrder.verify(preparedStatement).setString(1, "rod");
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
else {
inOrder.verify(resultSet).close();
}
inOrder.verify(preparedStatement).setString(1, "johnson");
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(resultSet).close();
inOrder.verify(preparedStatement).close();
inOrder.verify(connection).close();
}
}
@@ -16,6 +16,7 @@
package org.springframework.jdbc.core.simple;
import java.sql.Types;
import java.util.List;
import java.util.Map;
@@ -165,6 +166,34 @@ class JdbcClientIntegrationTests {
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)
@@ -179,6 +208,20 @@ class JdbcClientIntegrationTests {
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)
.update();
assertThat(rowsAffected).containsExactly(1, 1);
assertNumUsers(3);
assertUser(1, "Jane", "Smith");
assertUser(2, "John", "Doe");
}
@Test
void batchUpdateWithIndividualIndexedParameters() {
int[] rowsAffected = this.jdbcClient.sql(INSERT_WITH_JDBC_PARAMS)
@@ -35,6 +35,9 @@ import javax.sql.DataSource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InOrder;
import org.springframework.jdbc.Customer;
import org.springframework.jdbc.core.SqlParameterValue;
@@ -45,6 +48,7 @@ import org.springframework.jdbc.support.KeyHolder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -75,23 +79,23 @@ class JdbcClientNamedParameterTests {
private static final String[] COLUMN_NAMES = {"id", "forename"};
private Connection connection = mock();
private final Connection connection = mock();
private DataSource dataSource = mock();
private final DataSource dataSource = mock();
private PreparedStatement preparedStatement = mock();
private final PreparedStatement preparedStatement = mock();
private ResultSet resultSet = mock();
private final ResultSet resultSet = mock();
private ResultSetMetaData resultSetMetaData = mock();
private final ResultSetMetaData resultSetMetaData = mock();
private DatabaseMetaData databaseMetaData = mock();
private final DatabaseMetaData databaseMetaData = mock();
private JdbcClient client = JdbcClient.create(dataSource);
private final JdbcClient client = JdbcClient.create(dataSource);
private Map<String, Object> params = new HashMap<>();
private final Map<String, Object> params = new HashMap<>();
private MapSqlParameterSource paramSource = new MapSqlParameterSource();
private final MapSqlParameterSource paramSource = new MapSqlParameterSource();
@BeforeEach
@@ -390,6 +394,66 @@ class JdbcClientNamedParameterTests {
verify(connection).close();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void batchUpdateSingleRow(boolean supportsBatchUpdates) throws SQLException {
if (supportsBatchUpdates) {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(preparedStatement.executeBatch()).willReturn(new int[] {1});
}
else {
given(preparedStatement.executeUpdate()).willReturn(1);
}
int[] rowsAffected = client.sql(UPDATE_NAMED_PARAMETERS).batch()
.param("perfId", 1).param("priceId", 1).add()
.update();
assertThat(rowsAffected).containsExactly(1);
InOrder inOrder = inOrder(connection, preparedStatement);
inOrder.verify(connection).prepareStatement(UPDATE_NAMED_PARAMETERS_PARSED);
inOrder.verify(preparedStatement).setObject(1, 1);
inOrder.verify(preparedStatement).setObject(2, 1);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).close();
inOrder.verify(connection).close();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void batchUpdateMultipleRows(boolean supportsBatchUpdates) throws SQLException {
if (supportsBatchUpdates) {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(preparedStatement.executeBatch()).willReturn(new int[] {1, 1});
}
else {
given(preparedStatement.executeUpdate()).willReturn(1);
}
int[] rowsAffected = client.sql(UPDATE_NAMED_PARAMETERS).batch()
.param("perfId", 1).param("priceId", 1).add()
.param("perfId", 2).param("priceId", 2).add()
.update();
assertThat(rowsAffected).containsExactly(1, 1);
InOrder inOrder = inOrder(connection, preparedStatement);
inOrder.verify(connection).prepareStatement(UPDATE_NAMED_PARAMETERS_PARSED);
inOrder.verify(preparedStatement).setObject(1, 1);
inOrder.verify(preparedStatement).setObject(2, 1);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).setObject(1, 2);
inOrder.verify(preparedStatement).setObject(2, 2);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).close();
inOrder.verify(connection).close();
}
@Test
void updateWithTypedParameters() throws SQLException {
given(preparedStatement.executeUpdate()).willReturn(1);
@@ -406,6 +470,80 @@ class JdbcClientNamedParameterTests {
verify(connection).close();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void batchUpdateWithTypedParameters(boolean supportsBatchUpdates) throws SQLException {
if (supportsBatchUpdates) {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(preparedStatement.executeBatch()).willReturn(new int[] {1, 1});
}
else {
given(preparedStatement.executeUpdate()).willReturn(1);
}
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()
.update();
assertThat(rowsAffected).containsExactly(1, 1);
InOrder inOrder = inOrder(connection, preparedStatement);
inOrder.verify(connection).prepareStatement(UPDATE_NAMED_PARAMETERS_PARSED);
inOrder.verify(preparedStatement).setObject(1, 1, Types.DECIMAL);
inOrder.verify(preparedStatement).setObject(2, 1, Types.INTEGER);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).setObject(1, 2, Types.DECIMAL);
inOrder.verify(preparedStatement).setObject(2, 2, Types.INTEGER);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).close();
inOrder.verify(connection).close();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void batchUpdateWithParametersAndSqlType(boolean supportsBatchUpdates) throws SQLException {
if (supportsBatchUpdates) {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(preparedStatement.executeBatch()).willReturn(new int[] {1, 1});
}
else {
given(preparedStatement.executeUpdate()).willReturn(1);
}
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()
.update();
assertThat(rowsAffected).containsExactly(1, 1);
InOrder inOrder = inOrder(connection, preparedStatement);
inOrder.verify(connection).prepareStatement(UPDATE_NAMED_PARAMETERS_PARSED);
inOrder.verify(preparedStatement).setObject(1, 1, Types.DECIMAL);
inOrder.verify(preparedStatement).setObject(2, 1, Types.INTEGER);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).setObject(1, 2, Types.DECIMAL);
inOrder.verify(preparedStatement).setObject(2, 2, Types.INTEGER);
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(preparedStatement).close();
inOrder.verify(connection).close();
}
@Test
void updateWithGeneratedKeys() throws SQLException {
given(resultSetMetaData.getColumnCount()).willReturn(1);
@@ -430,28 +568,49 @@ class JdbcClientNamedParameterTests {
verify(connection).close();
}
@Test
void batchUpdateWithGeneratedKeys() throws SQLException {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void batchUpdateWithGeneratedKeys(boolean supportsBatchUpdates) throws SQLException {
if (supportsBatchUpdates) {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(preparedStatement.executeBatch()).willReturn(new int[] {1, 1});
}
else {
given(preparedStatement.executeUpdate()).willReturn(1);
}
given(resultSetMetaData.getColumnCount()).willReturn(1);
given(resultSetMetaData.getColumnLabel(1)).willReturn("1");
given(resultSetMetaData.getColumnLabel(1)).willReturn("id");
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getObject(1)).willReturn(11);
given(preparedStatement.executeUpdate()).willReturn(1);
given(resultSet.next()).willReturn(true, true, false);
given(resultSet.getObject(1)).willReturn(11, 12);
given(preparedStatement.getGeneratedKeys()).willReturn(resultSet);
given(connection.prepareStatement(INSERT_GENERATE_KEYS_PARSED, PreparedStatement.RETURN_GENERATED_KEYS))
.willReturn(preparedStatement);
KeyHolder generatedKeyHolder = new GeneratedKeyHolder();
int[] rowsAffected = client.sql(INSERT_GENERATE_KEYS).batch().param("name", "rod").add().update(generatedKeyHolder);
int[] rowsAffected = client.sql(INSERT_GENERATE_KEYS).batch()
.param("name", "rod").add()
.param("name", "johnson").add()
.update(generatedKeyHolder);
assertThat(rowsAffected).isEqualTo(new int[] { 1 });
assertThat(generatedKeyHolder.getKeyList()).hasSize(1);
assertThat(generatedKeyHolder.getKey()).isEqualTo(11);
verify(preparedStatement).setString(1, "rod");
verify(resultSet).close();
verify(preparedStatement).close();
verify(connection).close();
assertThat(rowsAffected).containsExactly(1, 1);
assertThat(generatedKeyHolder.getKeyList()).containsExactly(Map.of("id", 11), Map.of("id", 12));
InOrder inOrder = inOrder(connection, preparedStatement, resultSet);
inOrder.verify(connection).prepareStatement(INSERT_GENERATE_KEYS_PARSED, PreparedStatement.RETURN_GENERATED_KEYS);
inOrder.verify(preparedStatement).setString(1, "rod");
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
else {
inOrder.verify(resultSet).close();
}
inOrder.verify(preparedStatement).setString(1, "johnson");
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(resultSet).close();
inOrder.verify(preparedStatement).close();
inOrder.verify(connection).close();
}
@Test
@@ -478,28 +637,49 @@ class JdbcClientNamedParameterTests {
verify(connection).close();
}
@Test
void batchUpdateWithGeneratedKeysAndKeyColumnNames() throws SQLException {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void batchUpdateWithGeneratedKeysAndKeyColumnNames(boolean supportsBatchUpdates) throws SQLException {
if (supportsBatchUpdates) {
given(connection.getMetaData()).willReturn(databaseMetaData);
given(preparedStatement.executeBatch()).willReturn(new int[] {1, 1});
}
else {
given(preparedStatement.executeUpdate()).willReturn(1);
}
given(resultSetMetaData.getColumnCount()).willReturn(1);
given(resultSetMetaData.getColumnLabel(1)).willReturn("1");
given(resultSetMetaData.getColumnLabel(1)).willReturn("id");
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getObject(1)).willReturn(11);
given(preparedStatement.executeUpdate()).willReturn(1);
given(resultSet.next()).willReturn(true, true, false);
given(resultSet.getObject(1)).willReturn(11, 12);
given(preparedStatement.getGeneratedKeys()).willReturn(resultSet);
given(connection.prepareStatement(INSERT_GENERATE_KEYS_PARSED, new String[] {"id"}))
.willReturn(preparedStatement);
KeyHolder generatedKeyHolder = new GeneratedKeyHolder();
int[] rowsAffected = client.sql(INSERT_GENERATE_KEYS).batch().param("name", "rod").add().update(generatedKeyHolder, "id");
int[] rowsAffected = client.sql(INSERT_GENERATE_KEYS).batch()
.param("name", "rod").add()
.param("name", "johnson").add()
.update(generatedKeyHolder, "id");
assertThat(rowsAffected).isEqualTo(new int[] { 1 });
assertThat(generatedKeyHolder.getKeyList()).hasSize(1);
assertThat(generatedKeyHolder.getKey()).isEqualTo(11);
verify(preparedStatement).setString(1, "rod");
verify(resultSet).close();
verify(preparedStatement).close();
verify(connection).close();
assertThat(rowsAffected).containsExactly(1, 1);
assertThat(generatedKeyHolder.getKeyList()).containsExactly(Map.of("id", 11), Map.of("id", 12));
InOrder inOrder = inOrder(connection, preparedStatement, resultSet);
inOrder.verify(connection).prepareStatement(INSERT_GENERATE_KEYS_PARSED, new String[] {"id"});
inOrder.verify(preparedStatement).setString(1, "rod");
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
else {
inOrder.verify(resultSet).close();
}
inOrder.verify(preparedStatement).setString(1, "johnson");
if (supportsBatchUpdates) {
inOrder.verify(preparedStatement).addBatch();
}
inOrder.verify(resultSet).close();
inOrder.verify(preparedStatement).close();
inOrder.verify(connection).close();
}
}