Skip to content

Commit cd28c65

Browse files
committed
#73 - Introduce PreparedOperation.
We now encapsulate prepared operations from the StatementFactory within PreparedOperation that renders SQL and provides binding values. StatementFactory supports SELECT/INSERT/UPDATE/DELETE statement creation considering Dialect-specific rendering. StatementFactory replaces String-based statement methods in ReactiveDataAccessStrategy. PreparedOperation<Update> operation = accessStrategy.getStatements().update(entity.getTableName(), binder -> { binder.bind("name", "updated value"); binder.filterBy("id", SettableValue.from(42)); }); databaseClient.execute().sql(operation).then();
1 parent 75b6ede commit cd28c65

11 files changed

+969
-586
lines changed

src/main/java/org/springframework/data/r2dbc/function/BindIdOperation.java

-32
This file was deleted.

src/main/java/org/springframework/data/r2dbc/function/DatabaseClient.java

+5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.function.Supplier;
2727

2828
import org.reactivestreams.Publisher;
29+
2930
import org.springframework.data.domain.Pageable;
3031
import org.springframework.data.domain.Sort;
3132
import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator;
@@ -137,6 +138,9 @@ interface Builder {
137138
* Contract for specifying a SQL call along with options leading to the exchange. The SQL string can contain either
138139
* native parameter bind markers (e.g. {@literal $1, $2} for Postgres, {@literal @P0, @P1} for SQL Server) or named
139140
* parameters (e.g. {@literal :foo, :bar}) when {@link NamedParameterExpander} is enabled.
141+
* <p>
142+
* Accepts {@link PreparedOperation} as SQL and binding {@link Supplier}.
143+
* </p>
140144
*
141145
* @see NamedParameterExpander
142146
* @see DatabaseClient.Builder#namedParameters(NamedParameterExpander)
@@ -156,6 +160,7 @@ interface SqlSpec {
156160
*
157161
* @param sqlSupplier must not be {@literal null}.
158162
* @return a new {@link GenericExecuteSpec}.
163+
* @see PreparedOperation
159164
*/
160165
GenericExecuteSpec sql(Supplier<String> sqlSupplier);
161166
}

src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java

+38-27
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import java.util.LinkedHashSet;
3838
import java.util.List;
3939
import java.util.Map;
40-
import java.util.Set;
4140
import java.util.concurrent.atomic.AtomicBoolean;
4241
import java.util.function.BiFunction;
4342
import java.util.function.Function;
@@ -49,6 +48,7 @@
4948
import org.reactivestreams.Publisher;
5049

5150
import org.springframework.dao.DataAccessException;
51+
import org.springframework.dao.InvalidDataAccessApiUsageException;
5252
import org.springframework.data.domain.Pageable;
5353
import org.springframework.data.domain.Sort;
5454
import org.springframework.data.r2dbc.UncategorizedR2dbcException;
@@ -57,6 +57,7 @@
5757
import org.springframework.data.r2dbc.function.connectionfactory.ConnectionProxy;
5858
import org.springframework.data.r2dbc.function.convert.ColumnMapRowMapper;
5959
import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator;
60+
import org.springframework.data.relational.core.sql.Insert;
6061
import org.springframework.jdbc.core.SqlProvider;
6162
import org.springframework.lang.Nullable;
6263
import org.springframework.util.Assert;
@@ -337,9 +338,17 @@ <T> FetchSpec<T> exchange(String sql, BiFunction<Row, RowMetadata, T> mappingFun
337338
logger.debug("Executing SQL statement [" + sql + "]");
338339
}
339340

341+
if (sqlSupplier instanceof PreparedOperation<?>) {
342+
return ((PreparedOperation<?>) sqlSupplier).bind(it.createStatement(sql));
343+
}
344+
340345
BindableOperation operation = namedParameters.expand(sql, dataAccessStrategy.getBindMarkersFactory(),
341346
new MapBindParameterSource(byName));
342347

348+
if (logger.isTraceEnabled()) {
349+
logger.trace("Expanded SQL [" + operation.toQuery() + "]");
350+
}
351+
343352
Statement statement = it.createStatement(operation.toQuery());
344353

345354
byName.forEach((name, o) -> {
@@ -367,6 +376,7 @@ <T> FetchSpec<T> exchange(String sql, BiFunction<Row, RowMetadata, T> mappingFun
367376

368377
public ExecuteSpecSupport bind(int index, Object value) {
369378

379+
assertNotPreparedOperation();
370380
Assert.notNull(value, () -> String.format("Value at index %d must not be null. Use bindNull(…) instead.", index));
371381

372382
Map<Integer, SettableValue> byIndex = new LinkedHashMap<>(this.byIndex);
@@ -377,6 +387,8 @@ public ExecuteSpecSupport bind(int index, Object value) {
377387

378388
public ExecuteSpecSupport bindNull(int index, Class<?> type) {
379389

390+
assertNotPreparedOperation();
391+
380392
Map<Integer, SettableValue> byIndex = new LinkedHashMap<>(this.byIndex);
381393
byIndex.put(index, SettableValue.empty(type));
382394

@@ -385,6 +397,8 @@ public ExecuteSpecSupport bindNull(int index, Class<?> type) {
385397

386398
public ExecuteSpecSupport bind(String name, Object value) {
387399

400+
assertNotPreparedOperation();
401+
388402
Assert.hasText(name, "Parameter name must not be null or empty!");
389403
Assert.notNull(value,
390404
() -> String.format("Value for parameter %s must not be null. Use bindNull(…) instead.", name));
@@ -397,6 +411,7 @@ public ExecuteSpecSupport bind(String name, Object value) {
397411

398412
public ExecuteSpecSupport bindNull(String name, Class<?> type) {
399413

414+
assertNotPreparedOperation();
400415
Assert.hasText(name, "Parameter name must not be null or empty!");
401416

402417
Map<String, SettableValue> byName = new LinkedHashMap<>(this.byName);
@@ -405,6 +420,12 @@ public ExecuteSpecSupport bindNull(String name, Class<?> type) {
405420
return createInstance(this.byIndex, byName, this.sqlSupplier);
406421
}
407422

423+
private void assertNotPreparedOperation() {
424+
if (sqlSupplier instanceof PreparedOperation<?>) {
425+
throw new InvalidDataAccessApiUsageException("Cannot add bindings to a PreparedOperation");
426+
}
427+
}
428+
408429
protected ExecuteSpecSupport createInstance(Map<Integer, SettableValue> byIndex, Map<String, SettableValue> byName,
409430
Supplier<String> sqlSupplier) {
410431
return new ExecuteSpecSupport(byIndex, byName, sqlSupplier);
@@ -882,20 +903,19 @@ private <R> FetchSpec<R> exchange(BiFunction<Row, RowMetadata, R> mappingFunctio
882903
throw new IllegalStateException("Insert fields is empty!");
883904
}
884905

885-
BindableOperation bindableInsert = dataAccessStrategy.insertAndReturnGeneratedKeys(table, byName.keySet());
906+
PreparedOperation<Insert> operation = dataAccessStrategy.getStatements().insert(table, Collections.emptyList(),
907+
it -> {
908+
byName.forEach(it::bind);
909+
});
886910

887-
String sql = bindableInsert.toQuery();
911+
String sql = operation.toQuery();
888912
Function<Connection, Statement> insertFunction = it -> {
889913

890914
if (logger.isDebugEnabled()) {
891915
logger.debug("Executing SQL statement [" + sql + "]");
892916
}
893917

894-
Statement statement = it.createStatement(sql).returnGeneratedValues();
895-
896-
byName.forEach((k, v) -> bindableInsert.bind(statement, k, v));
897-
898-
return statement;
918+
return operation.bind(it.createStatement(sql));
899919
};
900920

901921
Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(insertFunction.apply(it).execute());
@@ -999,34 +1019,25 @@ private <MR> FetchSpec<MR> exchange(Object toInsert, BiFunction<Row, RowMetadata
9991019

10001020
OutboundRow outboundRow = dataAccessStrategy.getOutboundRow(toInsert);
10011021

1002-
Set<String> columns = new LinkedHashSet<>();
1003-
1004-
outboundRow.forEach((k, v) -> {
1005-
1006-
if (v.hasValue()) {
1007-
columns.add(k);
1008-
}
1009-
});
1022+
PreparedOperation<Insert> operation = dataAccessStrategy.getStatements().insert(table, Collections.emptyList(),
1023+
it -> {
1024+
outboundRow.forEach((k, v) -> {
10101025

1011-
BindableOperation bindableInsert = dataAccessStrategy.insertAndReturnGeneratedKeys(table, columns);
1026+
if (v.hasValue()) {
1027+
it.bind(k, v);
1028+
}
1029+
});
1030+
});
10121031

1013-
String sql = bindableInsert.toQuery();
1032+
String sql = operation.toQuery();
10141033

10151034
Function<Connection, Statement> insertFunction = it -> {
10161035

10171036
if (logger.isDebugEnabled()) {
10181037
logger.debug("Executing SQL statement [" + sql + "]");
10191038
}
10201039

1021-
Statement statement = it.createStatement(sql).returnGeneratedValues();
1022-
1023-
outboundRow.forEach((k, v) -> {
1024-
if (v.hasValue()) {
1025-
bindableInsert.bind(statement, k, v);
1026-
}
1027-
});
1028-
1029-
return statement;
1040+
return operation.bind(it.createStatement(sql));
10301041
};
10311042

10321043
Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(insertFunction.apply(it).execute());

0 commit comments

Comments
 (0)