Skip to content

Commit 9dbca5b

Browse files
Move over to UpsertRenderer.
1 parent c62097e commit 9dbca5b

20 files changed

+556
-370
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ public <T> T upsert(T instance) {
274274
Assert.notNull(instance, "Aggregate instance must not be null");
275275

276276
Class<T> entityType = (Class<T>) ClassUtils.getUserClass(instance);
277-
accessStrategy.upsert(instance, entityType, Identifier.empty());
277+
accessStrategy.upsert(instance, entityType);
278278
return instance;
279279
}
280280

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/CascadingDataAccessStrategy.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package org.springframework.data.jdbc.core.convert;
1717

18-
import static java.lang.Boolean.*;
18+
import static java.lang.Boolean.TRUE;
1919

2020
import java.util.ArrayList;
2121
import java.util.List;
@@ -25,7 +25,6 @@
2525
import java.util.stream.Stream;
2626

2727
import org.jspecify.annotations.Nullable;
28-
2928
import org.springframework.data.domain.Pageable;
3029
import org.springframework.data.domain.Sort;
3130
import org.springframework.data.mapping.PersistentPropertyPath;
@@ -49,6 +48,7 @@
4948
* @author Chirag Tailor
5049
* @author Diego Krupitza
5150
* @author Sergey Korotaev
51+
* @author Christoph Strobl
5252
* @since 1.1
5353
*/
5454
public class CascadingDataAccessStrategy implements DataAccessStrategy {
@@ -88,8 +88,8 @@ public NamedParameterJdbcOperations getJdbcOperations() {
8888
}
8989

9090
@Override
91-
public <T> int upsert(T instance, Class<T> domainType, Identifier identifier) {
92-
return collect(das -> das.upsert(instance, domainType, identifier));
91+
public <T> int upsert(T instance, Class<? super T> domainType) {
92+
return collect(das -> das.upsert(instance, domainType));
9393
}
9494

9595
@Override

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
* @author Chirag Tailor
4646
* @author Diego Krupitza
4747
* @author Sergey Korotaev
48+
* @author Christoph Strobl
4849
*/
4950
public interface DataAccessStrategy extends ReadingDataAccessStrategy, RelationResolver {
5051

@@ -119,18 +120,17 @@ public interface DataAccessStrategy extends ReadingDataAccessStrategy, RelationR
119120
<T> boolean updateWithVersion(T instance, Class<T> domainType, Number previousVersion);
120121

121122
/**
122-
* Upserts the data of a single entity (insert if row for id does not exist, update if it exists). Requires a
123-
* provided id. Only supported when the dialect supports single-statement upsert.
123+
* Upserts the data of a single entity (insert if row for id does not exist, update if it exists). Requires the
124+
* instance to hold an id. Only supported when the dialect supports single-statement upsert.
124125
*
125126
* @param instance the instance to upsert. Must not be {@code null}. Must have an id set.
126127
* @param domainType the type of the instance. Must not be {@code null}.
127-
* @param identifier information about data that needs to be considered (e.g. back-references). May be empty for root.
128128
* @param <T> the type of the instance.
129129
* @return the number of rows affected by the upsert.
130130
* @throws UnsupportedOperationException if the dialect does not support upsert.
131131
* @since 4.x
132132
*/
133-
<T> int upsert(T instance, Class<T> domainType, Identifier identifier);
133+
<T> int upsert(T instance, Class<? super T> domainType);
134134

135135
/**
136136
* Deletes a single row identified by the id, from the table identified by the domainType. Does not handle cascading

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package org.springframework.data.jdbc.core.convert;
1717

18-
import static org.springframework.data.jdbc.core.convert.SqlGenerator.*;
18+
import static org.springframework.data.jdbc.core.convert.SqlGenerator.VERSION_SQL_PARAMETER;
1919

2020
import java.sql.ResultSet;
2121
import java.sql.SQLException;
@@ -27,7 +27,6 @@
2727
import org.apache.commons.logging.Log;
2828
import org.apache.commons.logging.LogFactory;
2929
import org.jspecify.annotations.Nullable;
30-
3130
import org.springframework.dao.EmptyResultDataAccessException;
3231
import org.springframework.data.domain.Pageable;
3332
import org.springframework.data.domain.Sort;
@@ -184,14 +183,14 @@ public <S> boolean updateWithVersion(S instance, Class<S> domainType, Number pre
184183
}
185184

186185
@Override
187-
public <T> int upsert(T instance, Class<T> domainType, Identifier identifier) {
186+
public <T> int upsert(T instance, Class<? super T> domainType) {
188187

189-
SqlIdentifierParameterSource parameterSource = sqlParametersFactory.forInsert(instance, domainType, identifier,
188+
SqlIdentifierParameterSource parameterSource = sqlParametersFactory.forInsert(instance, domainType, Identifier.empty(),
190189
IdValueSource.PROVIDED);
191190

192191
String statement = sql(domainType).getUpsert(parameterSource.getIdentifiers());
193192

194-
if(logger.isTraceEnabled()) {
193+
if (logger.isTraceEnabled()) {
195194
logger.trace("Upsert: [%s]".formatted(statement));
196195
}
197196

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ public NamedParameterJdbcOperations getJdbcOperations() {
8181
}
8282

8383
@Override
84-
public <T> int upsert(T instance, Class<T> domainType, Identifier identifier) {
85-
return delegate.upsert(instance, domainType, identifier);
84+
public <T> int upsert(T instance, Class<? super T> domainType) {
85+
return delegate.upsert(instance, domainType);
8686
}
8787

8888
@Override

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ public void setNamespaceStrategy(NamespaceStrategy namespaceStrategy) {
185185
}
186186

187187
@Override
188-
public <T> int upsert(T instance, Class<T> domainType, Identifier identifier) {
188+
public <T> int upsert(T instance, Class<? super T> domainType) {
189189
throw new UnsupportedOperationException("Upsert is not supported by MyBatisDataAccessStrategy");
190190
}
191191

spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ConflictColumnCollector.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,25 @@
2222
import org.springframework.data.relational.core.sql.Comparison;
2323
import org.springframework.data.relational.core.sql.Condition;
2424
import org.springframework.data.relational.core.sql.MultipleCondition;
25-
import org.springframework.data.relational.core.sql.SqlIdentifier;
2625
import org.springframework.data.relational.core.sql.Visitable;
2726
import org.springframework.data.relational.core.sql.Visitor;
2827

2928
/**
30-
* Collects conflict columns from a {@link Condition} by traversing equality comparisons.
31-
* For {@link Comparison} with {@code =} and a {@link Column} on the left, the column name is collected.
32-
* For {@link MultipleCondition} (e.g. AND), recurses into child conditions.
29+
* Collects conflict columns from a {@link Condition} by traversing equality comparisons. For {@link Comparison} with
30+
* {@code =} and a {@link Column} on the left, the column name is collected. For {@link MultipleCondition} (e.g. AND),
31+
* recurses into child conditions.
3332
*
3433
* @since 4.x
3534
*/
3635
final class ConflictColumnCollector implements Visitor {
3736

38-
private final List<SqlIdentifier> conflictColumns = new ArrayList<>();
37+
private final List<Column> conflictColumns = new ArrayList<>();
3938

4039
@Override
4140
public void enter(Visitable segment) {
4241

43-
if (segment instanceof Comparison comparison && "=".equals(comparison.getComparator())
44-
&& comparison.getLeft() instanceof Column column) {
45-
conflictColumns.add(column.getName());
42+
if (segment instanceof Comparison comparison && comparison.getLeft() instanceof Column column) {
43+
conflictColumns.add(column);
4644
}
4745

4846
if (segment instanceof MultipleCondition multiple) {
@@ -52,7 +50,7 @@ public void enter(Visitable segment) {
5250
}
5351
}
5452

55-
List<SqlIdentifier> getConflictColumns() {
53+
List<Column> getConflictColumns() {
5654
return conflictColumns;
5755
}
5856
}

spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/MySqlUpsertRenderContext.java

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,6 @@
1515
*/
1616
package org.springframework.data.relational.core.sql.render;
1717

18-
import java.util.List;
19-
import java.util.function.Function;
20-
import java.util.stream.Collectors;
21-
22-
import org.springframework.data.relational.core.sql.SqlIdentifier;
23-
import org.springframework.data.relational.core.sql.Table;
24-
import org.springframework.util.Assert;
25-
2618
/**
2719
* MySQL / MariaDB upsert using {@code INSERT ... ON DUPLICATE KEY UPDATE}.
2820
*
@@ -34,34 +26,7 @@ public enum MySqlUpsertRenderContext implements UpsertRenderContext {
3426
INSTANCE;
3527

3628
@Override
37-
public String renderUpsert(Table table, Columns columns, Function<SqlIdentifier, String> bindMarkerFn) {
38-
39-
Assert.notEmpty(columns.insertColumns(), "Insert columns must not be empty");
40-
Assert.notEmpty(columns.filterColumns(), "Filter columns must not be empty");
41-
42-
String tableName = columns.tableName(table);
43-
String columnNames = String.join(", ", columns.insertColumnNames());
44-
String bindMarkers = String.join(", ", columns.insertColumnBindMarkers(bindMarkerFn));
45-
String setValues = setValuesSnippet(columns);
46-
47-
return "INSERT INTO %s (%s) VALUES (%s) ON DUPLICATE KEY UPDATE %s".formatted( //
48-
tableName, //
49-
columnNames, //
50-
bindMarkers, //
51-
setValues);
52-
}
53-
54-
private static String setValuesSnippet(Columns columns) {
55-
56-
List<SqlIdentifier> updateColumns = columns.updateColumns();
57-
58-
if (updateColumns.isEmpty()) {
59-
updateColumns = columns.filterColumns();
60-
}
61-
62-
return updateColumns.stream().map(col -> {
63-
String colName = col.toSql(columns.identifierProcessing());
64-
return "%s = VALUES(%s)".formatted(colName, colName);
65-
}).collect(Collectors.joining(", "));
29+
public UpsertStatementRenderer renderer() {
30+
return UpsertStatementRenderer.mySql();
6631
}
6732
}

spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/OracleUpsertRenderContext.java

Lines changed: 2 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@
1515
*/
1616
package org.springframework.data.relational.core.sql.render;
1717

18-
import java.util.List;
19-
import java.util.function.Function;
20-
21-
import org.springframework.data.relational.core.sql.SqlIdentifier;
22-
import org.springframework.data.relational.core.sql.Table;
23-
import org.springframework.util.Assert;
24-
2518
/**
2619
* Oracle MERGE upsert. Uses {@code SELECT ... FROM DUAL} for source values.
2720
*
@@ -32,55 +25,7 @@ public enum OracleUpsertRenderContext implements UpsertRenderContext {
3225
INSTANCE;
3326

3427
@Override
35-
public String renderUpsert(Table table, Columns columns, Function<SqlIdentifier, String> bindMarkerFn) {
36-
37-
Assert.notEmpty(columns.insertColumns(), "Insert columns must not be empty");
38-
Assert.notEmpty(columns.filterColumns(), "Filter columns must not be empty");
39-
40-
String targetTableAlias = columns.identifierProcessing().quote(StandardSqlUpsertRenderContext.targetTableAlias);
41-
String sourceTableAlias = columns.identifierProcessing().quote(StandardSqlUpsertRenderContext.sourceTableAlias);
42-
43-
String tableName = columns.tableName(table);
44-
String insertColumnNames = String.join(", ", columns.insertColumnNames());
45-
String sourceSelectList = String.join(", ",
46-
columns.insertColumns().stream().map(col -> bindMarkerFn.apply(col) + " AS " + columns.column(col)).toList());
47-
48-
String onCondition = String.join(" AND ", columns.filterColumns().stream().map(col -> {
49-
String colName = columns.column(col);
50-
return "%s.%s = %s.%s".formatted(targetTableAlias, colName, sourceTableAlias, colName);
51-
}).toList());
52-
53-
String insertValuesSql = String.join(", ",
54-
columns.insertColumns().stream().map(col -> columns.column(sourceTableAlias, col)).toList());
55-
56-
String insertClause = "WHEN NOT MATCHED THEN INSERT (%s) VALUES (%s)".formatted(insertColumnNames,
57-
insertValuesSql);
58-
59-
List<SqlIdentifier> updateColumns = columns.updateColumns();
60-
if (updateColumns.isEmpty()) {
61-
// ORA-38104: columns referenced in ON cannot be updated; omit WHEN MATCHED so existing rows are left
62-
// unchanged (same as a no-op update of key-only columns).
63-
return "MERGE INTO %s %s USING (SELECT %s FROM DUAL) %s ON (%s) %s".formatted( //
64-
tableName, //
65-
targetTableAlias, //
66-
sourceSelectList, //
67-
sourceTableAlias, //
68-
onCondition, //
69-
insertClause);
70-
}
71-
72-
String updateSetClause = String.join(", ", updateColumns.stream().map(col -> {
73-
String colName = columns.column(col);
74-
return "%s.%s = %s.%s".formatted(targetTableAlias, colName, sourceTableAlias, colName);
75-
}).toList());
76-
77-
return "MERGE INTO %s %s USING (SELECT %s FROM DUAL) %s ON (%s) WHEN MATCHED THEN UPDATE SET %s %s".formatted( //
78-
tableName, //
79-
targetTableAlias, //
80-
sourceSelectList, //
81-
sourceTableAlias, //
82-
onCondition, //
83-
updateSetClause, //
84-
insertClause);
28+
public UpsertStatementRenderer renderer() {
29+
return UpsertStatementRenderer.oracle();
8530
}
8631
}

spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/PostgresUpsertRenderContext.java

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,6 @@
1515
*/
1616
package org.springframework.data.relational.core.sql.render;
1717

18-
import java.util.List;
19-
import java.util.function.Function;
20-
import java.util.stream.Collectors;
21-
22-
import org.springframework.data.relational.core.sql.SqlIdentifier;
23-
import org.springframework.data.relational.core.sql.Table;
24-
import org.springframework.util.Assert;
25-
2618
/**
2719
* PostgreSQL upsert using {@code INSERT ... ON CONFLICT ... DO UPDATE SET}.
2820
*
@@ -33,44 +25,7 @@ public enum PostgresUpsertRenderContext implements UpsertRenderContext {
3325
INSTANCE;
3426

3527
@Override
36-
public String renderUpsert(Table table, Columns columns, Function<SqlIdentifier, String> bindMarkerFn) {
37-
38-
Assert.notEmpty(columns.insertColumns(), "Insert columns must not be empty");
39-
Assert.notEmpty(columns.filterColumns(), "Filter columns must not be empty");
40-
41-
String tableName = columns.tableName(table);
42-
String insertColumnNames = String.join(", ", columns.insertColumnNames());
43-
String bindMarkers = String.join(", ", columns.insertColumnBindMarkers(bindMarkerFn));
44-
String filterColumnNames = String.join(", ", columns.filterColumnNames());
45-
46-
if(columns.updateColumns().isEmpty()) {
47-
return "INSERT INTO %s (%s) VALUES (%s) ON CONFLICT (%s) DO NOTHING".formatted(//
48-
tableName, //
49-
insertColumnNames, //
50-
bindMarkers, //
51-
filterColumnNames);
52-
}
53-
54-
String setValues = setValuesSnippet(columns);
55-
return "INSERT INTO %s (%s) VALUES (%s) ON CONFLICT (%s) DO UPDATE SET %s".formatted(//
56-
tableName, //
57-
insertColumnNames, //
58-
bindMarkers, //
59-
filterColumnNames, //
60-
setValues);
61-
}
62-
63-
private static String setValuesSnippet(Columns columns) {
64-
65-
List<SqlIdentifier> updateColumns = columns.updateColumns();
66-
67-
if (updateColumns.isEmpty()) {
68-
updateColumns = columns.filterColumns();
69-
}
70-
71-
return updateColumns.stream().map(col -> {
72-
String colName = col.toSql(columns.identifierProcessing());
73-
return "%s = EXCLUDED.%s".formatted(colName, colName);
74-
}).collect(Collectors.joining(", "));
28+
public UpsertStatementRenderer renderer() {
29+
return UpsertStatementRenderer.postgres();
7530
}
7631
}

0 commit comments

Comments
 (0)