Add DatabaseClient bind variant for list of parameters

Prior to this commit, the `DatabaseClient` interface would allow batch
operations for binding parameters by their names and values. Positional
parameters did not have such equivalent.

This commit adds a new `bindValues(List<?>)` method variant for adding
multiple positional arguments in a single call and avoiding allocation
overhead when the parameters count is large.

Closes gh-33274
This commit is contained in:
Brian Clozel 2024-07-29 14:07:15 +02:00
parent 1f2c6c33ac
commit 38453910cd
4 changed files with 70 additions and 1 deletions

View File

@ -364,6 +364,28 @@ Or you may pass in a parameter object with bean properties or record components:
.bindProperties(new Person("joe", "Joe", 34); .bindProperties(new Person("joe", "Joe", 34);
---- ----
Alternatively, you can use positional parameters for binding values to statements.
Indices are zero based.
[source,java]
----
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind(0, "joe")
.bind(1, "Joe")
.bind(2, 34);
----
In case your application is binding to many parameters, the same can be achieved with a single call:
[source,java]
----
List<?> values = List.of("joe", "Joe", 34);
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bindValues(values);
----
.R2DBC Native Bind Markers .R2DBC Native Bind Markers
**** ****
R2DBC uses database-native bind markers that depend on the actual database vendor. R2DBC uses database-native bind markers that depend on the actual database vendor.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,7 @@
package org.springframework.r2dbc.core; package org.springframework.r2dbc.core;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -56,6 +57,7 @@ import org.springframework.util.Assert;
* *
* @author Mark Paluch * @author Mark Paluch
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Brian Clozel
* @since 5.3 * @since 5.3
*/ */
public interface DatabaseClient extends ConnectionAccessor { public interface DatabaseClient extends ConnectionAccessor {
@ -191,6 +193,18 @@ public interface DatabaseClient extends ConnectionAccessor {
*/ */
GenericExecuteSpec bindNull(String name, Class<?> type); GenericExecuteSpec bindNull(String name, Class<?> type);
/**
* Bind the parameter values from the given source list,
* registering each as a positional parameter using their order
* in the given list as their index.
* @param source the source list of parameters, with their order
* as position and each value either a scalar value
* or a {@link io.r2dbc.spi.Parameter}
* @since 6.2
* @see #bind(int, Object)
*/
GenericExecuteSpec bindValues(List<?> source);
/** /**
* Bind the parameter values from the given source map, * Bind the parameter values from the given source map,
* registering each as a parameter with the map key as name. * registering each as a parameter with the map key as name.

View File

@ -24,6 +24,7 @@ import java.lang.reflect.Proxy;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.ListIterator;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction; import java.util.function.BiFunction;
@ -308,6 +309,18 @@ final class DefaultDatabaseClient implements DatabaseClient {
return new DefaultGenericExecuteSpec(this.byIndex, byName, this.sqlSupplier, this.filterFunction); return new DefaultGenericExecuteSpec(this.byIndex, byName, this.sqlSupplier, this.filterFunction);
} }
@Override
public GenericExecuteSpec bindValues(List<?> source) {
assertNotPreparedOperation();
Assert.notNull(source, "Source list must not be null");
Map<Integer, Parameter> byIndex = new LinkedHashMap<>(this.byIndex);
ListIterator<?> listIterator = source.listIterator();
while (listIterator.hasNext()) {
byIndex.put(listIterator.nextIndex(), resolveParameter(listIterator.next()));
}
return new DefaultGenericExecuteSpec(byIndex, this.byName, this.sqlSupplier, this.filterFunction);
}
@Override @Override
public GenericExecuteSpec bindValues(Map<String, ?> source) { public GenericExecuteSpec bindValues(Map<String, ?> source) {
assertNotPreparedOperation(); assertNotPreparedOperation();

View File

@ -16,6 +16,7 @@
package org.springframework.r2dbc.core; package org.springframework.r2dbc.core;
import java.util.List;
import java.util.Map; import java.util.Map;
import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactory;
@ -96,6 +97,25 @@ abstract class AbstractDatabaseClientIntegrationTests {
.verifyComplete(); .verifyComplete();
} }
@Test
void executeInsertWithList() {
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
databaseClient.sql("INSERT INTO legoset (id, name, manual) VALUES(:id, :name, :manual)")
.bindValues(List.of(42055, Parameters.in("SCHAUFELRADBAGGER"), Parameters.in(Integer.class)))
.fetch().rowsUpdated()
.as(StepVerifier::create)
.expectNext(1L)
.verifyComplete();
databaseClient.sql("SELECT id FROM legoset")
.mapValue(Integer.class)
.first()
.as(StepVerifier::create)
.assertNext(actual -> assertThat(actual).isEqualTo(42055))
.verifyComplete();
}
@Test @Test
void executeInsertWithMap() { void executeInsertWithMap() {
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);