diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java index 2014658c92..fe0da9c63c 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java @@ -101,6 +101,7 @@ * @author Mikhail Polivakha * @author Jens Schauder * @author Christoph Strobl + * @author leewoo97 * @since 1.1 */ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAware, ApplicationContextAware { @@ -742,12 +743,19 @@ private Mono doUpsert(T entity, SqlIdentifier tableName, OutboundRow outb } List updateColumns = new ArrayList<>(); + Set insertOnlyColumns = new LinkedHashSet<>(); persistentEntity.forEach(p -> { - if (!p.isInsertOnly() && !identifierColumns.contains(p.getColumnName())) { - updateColumns.add(p.getColumnName()); + if (p.isInsertOnly()) { + insertOnlyColumns.add(p.getColumnName()); } }); + for (SqlIdentifier column : outboundRow.keySet()) { + if (!identifierColumns.contains(column) && !insertOnlyColumns.contains(column)) { + updateColumns.add(column); + } + } + upsert = upsert.withUpdateColumns(updateColumns); PreparedOperation operation = mapper.getMappedObject(upsert); diff --git a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/ReactiveUpsertOperationUnitTests.java b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/ReactiveUpsertOperationUnitTests.java index 5e00c43430..ad1fa8cdf5 100644 --- a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/ReactiveUpsertOperationUnitTests.java +++ b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/ReactiveUpsertOperationUnitTests.java @@ -28,6 +28,8 @@ import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; import org.springframework.data.r2dbc.testing.StatementRecorder; import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Embedded; +import org.springframework.data.relational.core.mapping.Table; import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.r2dbc.core.Parameter; @@ -35,6 +37,7 @@ * Unit tests for {@link ReactiveUpsertOperation}. * * @author Christoph Strobl + * @author leewoo97 */ public class ReactiveUpsertOperationUnitTests { @@ -158,6 +161,25 @@ void upsertIncludesInsertOnlyColumns() { assertThat(statement.getSql()).contains("insert_only"); } + @Test // GH-2313 + void upsertDoesNotUpdateEmbeddedIdProperty() { + + MockRowMetadata metadata = MockRowMetadata.builder().build(); + MockResult result = MockResult.builder().rowMetadata(metadata).rowsUpdated(1).build(); + + recorder.addStubbing(s -> s.startsWith("INSERT"), result); + + entityTemplate.upsert(new EntityWithEmbeddedId(new EntityId("one", "two"), "Walter")) // + .as(StepVerifier::create) // + .expectNextCount(1) // + .verifyComplete(); + + StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("INSERT")); + + assertThat(statement.getSql()).isEqualTo( + "INSERT INTO entity (col1, col2, name) VALUES ($1, $2, $3) ON CONFLICT (col1, col2) DO UPDATE SET name = EXCLUDED.name"); + } + static class Person { @Id Long id; @@ -184,4 +206,11 @@ public void setName(String name) { } } + @Table("entity") + record EntityWithEmbeddedId(@Id @Embedded.Empty EntityId id, String name) { + } + + record EntityId(String col1, String col2) { + } + }