Skip to content

Commit 3845391

Browse files
committed
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
1 parent 1f2c6c3 commit 3845391

File tree

4 files changed

+70
-1
lines changed

4 files changed

+70
-1
lines changed

framework-docs/modules/ROOT/pages/data-access/r2dbc.adoc

+22
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,28 @@ Or you may pass in a parameter object with bean properties or record components:
364364
.bindProperties(new Person("joe", "Joe", 34);
365365
----
366366

367+
Alternatively, you can use positional parameters for binding values to statements.
368+
Indices are zero based.
369+
370+
[source,java]
371+
----
372+
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
373+
.bind(0, "joe")
374+
.bind(1, "Joe")
375+
.bind(2, 34);
376+
----
377+
378+
In case your application is binding to many parameters, the same can be achieved with a single call:
379+
380+
[source,java]
381+
----
382+
List<?> values = List.of("joe", "Joe", 34);
383+
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
384+
.bindValues(values);
385+
----
386+
387+
388+
367389
.R2DBC Native Bind Markers
368390
****
369391
R2DBC uses database-native bind markers that depend on the actual database vendor.

spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DatabaseClient.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.r2dbc.core;
1818

19+
import java.util.List;
1920
import java.util.Map;
2021
import java.util.function.BiFunction;
2122
import java.util.function.Consumer;
@@ -56,6 +57,7 @@
5657
*
5758
* @author Mark Paluch
5859
* @author Juergen Hoeller
60+
* @author Brian Clozel
5961
* @since 5.3
6062
*/
6163
public interface DatabaseClient extends ConnectionAccessor {
@@ -191,6 +193,18 @@ interface GenericExecuteSpec {
191193
*/
192194
GenericExecuteSpec bindNull(String name, Class<?> type);
193195

196+
/**
197+
* Bind the parameter values from the given source list,
198+
* registering each as a positional parameter using their order
199+
* in the given list as their index.
200+
* @param source the source list of parameters, with their order
201+
* as position and each value either a scalar value
202+
* or a {@link io.r2dbc.spi.Parameter}
203+
* @since 6.2
204+
* @see #bind(int, Object)
205+
*/
206+
GenericExecuteSpec bindValues(List<?> source);
207+
194208
/**
195209
* Bind the parameter values from the given source map,
196210
* registering each as a parameter with the map key as name.

spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DefaultDatabaseClient.java

+13
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Collections;
2525
import java.util.LinkedHashMap;
2626
import java.util.List;
27+
import java.util.ListIterator;
2728
import java.util.Map;
2829
import java.util.concurrent.atomic.AtomicBoolean;
2930
import java.util.function.BiFunction;
@@ -308,6 +309,18 @@ public DefaultGenericExecuteSpec bindNull(String name, Class<?> type) {
308309
return new DefaultGenericExecuteSpec(this.byIndex, byName, this.sqlSupplier, this.filterFunction);
309310
}
310311

312+
@Override
313+
public GenericExecuteSpec bindValues(List<?> source) {
314+
assertNotPreparedOperation();
315+
Assert.notNull(source, "Source list must not be null");
316+
Map<Integer, Parameter> byIndex = new LinkedHashMap<>(this.byIndex);
317+
ListIterator<?> listIterator = source.listIterator();
318+
while (listIterator.hasNext()) {
319+
byIndex.put(listIterator.nextIndex(), resolveParameter(listIterator.next()));
320+
}
321+
return new DefaultGenericExecuteSpec(byIndex, this.byName, this.sqlSupplier, this.filterFunction);
322+
}
323+
311324
@Override
312325
public GenericExecuteSpec bindValues(Map<String, ?> source) {
313326
assertNotPreparedOperation();

spring-r2dbc/src/test/java/org/springframework/r2dbc/core/AbstractDatabaseClientIntegrationTests.java

+20
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.r2dbc.core;
1818

19+
import java.util.List;
1920
import java.util.Map;
2021

2122
import io.r2dbc.spi.ConnectionFactory;
@@ -96,6 +97,25 @@ void executeInsert() {
9697
.verifyComplete();
9798
}
9899

100+
@Test
101+
void executeInsertWithList() {
102+
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
103+
104+
databaseClient.sql("INSERT INTO legoset (id, name, manual) VALUES(:id, :name, :manual)")
105+
.bindValues(List.of(42055, Parameters.in("SCHAUFELRADBAGGER"), Parameters.in(Integer.class)))
106+
.fetch().rowsUpdated()
107+
.as(StepVerifier::create)
108+
.expectNext(1L)
109+
.verifyComplete();
110+
111+
databaseClient.sql("SELECT id FROM legoset")
112+
.mapValue(Integer.class)
113+
.first()
114+
.as(StepVerifier::create)
115+
.assertNext(actual -> assertThat(actual).isEqualTo(42055))
116+
.verifyComplete();
117+
}
118+
99119
@Test
100120
void executeInsertWithMap() {
101121
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);

0 commit comments

Comments
 (0)