Skip to content

Commit 67c3d49

Browse files
committed
#402 - Exclude id property using initial value when inserting objects.
We now exclude Id properties from being used in the INSERT field list if the Id value is zero and of a primitive type or if the value is null using a numeric wrapper type.
1 parent ecbb8d8 commit 67c3d49

File tree

3 files changed

+69
-12
lines changed

3 files changed

+69
-12
lines changed

src/main/asciidoc/reference/r2dbc-repositories.adoc

+7-3
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,9 @@ The ID of an entity must be annotated with Spring Data's https://docs.spring.io/
332332

333333
When your database has an auto-increment column for the ID column, the generated value gets set in the entity after inserting it into the database.
334334

335+
Spring Data R2DBC does not attempt to insert values of identifier columns when the entity is new and the identifier value defaults to its initial value.
336+
That is `0` for primitive types and `null` if the identifier property uses a numeric wrapper type such as `Long`.
337+
335338
One important constraint is that, after saving an entity, the entity must not be new anymore.
336339
Note that whether an entity is new is part of the entity's state.
337340
With auto-increment columns, this happens automatically, because the ID gets set by Spring Data with the value from the ID column.
@@ -340,7 +343,8 @@ With auto-increment columns, this happens automatically, because the ID gets set
340343
=== Optimistic Locking
341344

342345
The `@Version` annotation provides syntax similar to that of JPA in the context of R2DBC and makes sure updates are only applied to documents with a matching version.
343-
Therefore, the actual value of the version property is added to the update query in such a way that the update does not have any effect if another operation altered the document in the meantime. In that case, an `OptimisticLockingFailureException` is thrown.
346+
Therefore, the actual value of the version property is added to the update query in such a way that the update does not have any effect if another operation altered the document in the meantime.
347+
In that case, an `OptimisticLockingFailureException` is thrown.
344348
The following example shows these features:
345349

346350
====
@@ -370,8 +374,8 @@ template.save(other).subscribe(); // emits OptimisticLockingFailureException
370374
----
371375
<1> Initially insert row. `version` is set to `0`.
372376
<2> Load the just inserted row. `version` is still `0`.
373-
<3> Update the row with `version = 0`. Set the `lastname` and bump `version` to `1`.
374-
<4> Try to update the previously loaded document that still has `version = 0`. The operation fails with an `OptimisticLockingFailureException`, as the current `version` is `1`.
377+
<3> Update the row with `version = 0`.Set the `lastname` and bump `version` to `1`.
378+
<4> Try to update the previously loaded document that still has `version = 0`.The operation fails with an `OptimisticLockingFailureException`, as the current `version` is `1`.
375379
====
376380

377381
:projection-collection: Flux

src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java

+37-9
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
3838
import org.springframework.data.mapping.model.ParameterValueProvider;
3939
import org.springframework.data.r2dbc.mapping.OutboundRow;
40-
import org.springframework.data.r2dbc.mapping.SettableValue;
4140
import org.springframework.data.r2dbc.support.ArrayUtils;
4241
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
4342
import org.springframework.data.relational.core.conversion.RelationalConverter;
@@ -330,11 +329,11 @@ private void writeInternal(Object source, OutboundRow sink, Class<?> userClass)
330329
RelationalPersistentEntity<?> entity = getRequiredPersistentEntity(userClass);
331330
PersistentPropertyAccessor<?> propertyAccessor = entity.getPropertyAccessor(source);
332331

333-
writeProperties(sink, entity, propertyAccessor);
332+
writeProperties(sink, entity, propertyAccessor, entity.isNew(source));
334333
}
335334

336335
private void writeProperties(OutboundRow sink, RelationalPersistentEntity<?> entity,
337-
PersistentPropertyAccessor<?> accessor) {
336+
PersistentPropertyAccessor<?> accessor, boolean isNew) {
338337

339338
for (RelationalPersistentProperty property : entity) {
340339

@@ -350,18 +349,47 @@ private void writeProperties(OutboundRow sink, RelationalPersistentEntity<?> ent
350349
}
351350

352351
if (getConversions().isSimpleType(value.getClass())) {
353-
writeSimpleInternal(sink, value, property);
352+
writeSimpleInternal(sink, value, isNew, property);
354353
} else {
355-
writePropertyInternal(sink, value, property);
354+
writePropertyInternal(sink, value, isNew, property);
356355
}
357356
}
358357
}
359358

360-
private void writeSimpleInternal(OutboundRow sink, Object value, RelationalPersistentProperty property) {
361-
sink.put(property.getColumnName(), Parameter.from(getPotentiallyConvertedSimpleWrite(value)));
359+
private void writeSimpleInternal(OutboundRow sink, Object value, boolean isNew,
360+
RelationalPersistentProperty property) {
361+
362+
Object result = getPotentiallyConvertedSimpleWrite(value);
363+
364+
if (property.isIdProperty() && isNew) {
365+
if (shouldSkipIdValue(result, property)) {
366+
return;
367+
}
368+
}
369+
370+
sink.put(property.getColumnName(),
371+
Parameter.fromOrEmpty(result, getPotentiallyConvertedSimpleNullType(property.getType())));
372+
}
373+
374+
private boolean shouldSkipIdValue(@Nullable Object value, RelationalPersistentProperty property) {
375+
376+
if (value == null) {
377+
return true;
378+
}
379+
380+
if (!property.getType().isPrimitive()) {
381+
return value == null;
382+
}
383+
384+
if (Number.class.isInstance(value)) {
385+
return ((Number) value).longValue() == 0L;
386+
}
387+
388+
return false;
362389
}
363390

364-
private void writePropertyInternal(OutboundRow sink, Object value, RelationalPersistentProperty property) {
391+
private void writePropertyInternal(OutboundRow sink, Object value, boolean isNew,
392+
RelationalPersistentProperty property) {
365393

366394
TypeInformation<?> valueType = ClassTypeInformation.from(value.getClass());
367395

@@ -370,7 +398,7 @@ private void writePropertyInternal(OutboundRow sink, Object value, RelationalPer
370398
if (valueType.getActualType() != null && valueType.getRequiredActualType().isCollectionLike()) {
371399

372400
// pass-thru nested collections
373-
writeSimpleInternal(sink, value, property);
401+
writeSimpleInternal(sink, value, isNew, property);
374402
return;
375403
}
376404

src/test/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverterUnitTests.java

+25
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import io.r2dbc.spi.Row;
2222
import lombok.AllArgsConstructor;
23+
import lombok.RequiredArgsConstructor;
2324

2425
import java.time.Instant;
2526
import java.time.LocalDateTime;
@@ -189,6 +190,24 @@ public void shouldReadTopLevelEntity() {
189190
assertThat(result.entity).isNotNull();
190191
}
191192

193+
@Test // gh-402
194+
public void writeShouldSkipPrimitiveIdIfValueIsZero() {
195+
196+
OutboundRow row = new OutboundRow();
197+
converter.write(new WithPrimitiveId(0), row);
198+
199+
assertThat(row).isEmpty();
200+
}
201+
202+
@Test // gh-402
203+
public void writeShouldWritePrimitiveIdIfValueIsNonZero() {
204+
205+
OutboundRow row = new OutboundRow();
206+
converter.write(new WithPrimitiveId(1), row);
207+
208+
assertThat(row).containsEntry(SqlIdentifier.unquoted("id"), Parameter.fromOrEmpty(1L, Long.TYPE));
209+
}
210+
192211
@AllArgsConstructor
193212
static class Person {
194213
@Id String id;
@@ -214,6 +233,12 @@ static class PersonWithConversions {
214233
NonMappableEntity unsupported;
215234
}
216235

236+
@RequiredArgsConstructor
237+
static class WithPrimitiveId {
238+
239+
@Id final long id;
240+
}
241+
217242
static class CustomConversionPerson {
218243

219244
String foo;

0 commit comments

Comments
 (0)