mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-10-08 08:09:09 +00:00
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:
+44
-23
@@ -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
|
||||
|
||||
+217
-36
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+43
@@ -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)
|
||||
|
||||
+217
-37
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user