Skip to content

Commit

Permalink
Add DatabaseClient bind variant for list of parameters
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bclozel committed Jul 29, 2024
1 parent 1f2c6c3 commit 3845391
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 1 deletion.
22 changes: 22 additions & 0 deletions framework-docs/modules/ROOT/pages/data-access/r2dbc.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,28 @@ Or you may pass in a parameter object with bean properties or record components:
.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 uses database-native bind markers that depend on the actual database vendor.
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,7 @@

package org.springframework.r2dbc.core;

import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Consumer;
Expand Down Expand Up @@ -56,6 +57,7 @@
*
* @author Mark Paluch
* @author Juergen Hoeller
* @author Brian Clozel
* @since 5.3
*/
public interface DatabaseClient extends ConnectionAccessor {
Expand Down Expand Up @@ -191,6 +193,18 @@ interface GenericExecuteSpec {
*/
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,
* registering each as a parameter with the map key as name.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
Expand Down Expand Up @@ -308,6 +309,18 @@ public DefaultGenericExecuteSpec bindNull(String name, Class<?> type) {
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
public GenericExecuteSpec bindValues(Map<String, ?> source) {
assertNotPreparedOperation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.springframework.r2dbc.core;

import java.util.List;
import java.util.Map;

import io.r2dbc.spi.ConnectionFactory;
Expand Down Expand Up @@ -96,6 +97,25 @@ void executeInsert() {
.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
void executeInsertWithMap() {
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
Expand Down

0 comments on commit 3845391

Please sign in to comment.