From c80873019c00d4ebf0b4588c78bc4e2deb4e155c Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 4 Jan 2024 15:00:38 +0100 Subject: [PATCH] Consider registered converters using Row and RowDocument as source. We now consider converters for RowDocument. Additionally, we reinstated conversion from R2DBC's Row type into entities as that converter functionality got lost during the converter revision. Closes #1710 --- .../data/r2dbc/core/R2dbcEntityTemplate.java | 9 ++- .../core/R2dbcEntityTemplateUnitTests.java | 67 ++++++++++++++++++- .../MappingRelationalConverter.java | 18 +++-- 3 files changed, 87 insertions(+), 7 deletions(-) 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 b119e1cdd..febbfb2a6 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 @@ -57,6 +57,7 @@ import org.springframework.data.r2dbc.mapping.event.AfterConvertCallback; import org.springframework.data.r2dbc.mapping.event.AfterSaveCallback; import org.springframework.data.r2dbc.mapping.event.BeforeConvertCallback; import org.springframework.data.r2dbc.mapping.event.BeforeSaveCallback; +import org.springframework.data.relational.core.conversion.AbstractRelationalConverter; import org.springframework.data.relational.core.mapping.PersistentPropertyTranslator; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -818,7 +819,13 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw BiFunction rowMapper; - if (simpleType) { + // Bridge-code: Consider Converter until we have fully migrated to RowDocument + if (converter instanceof AbstractRelationalConverter relationalConverter + && relationalConverter.getConversions().hasCustomReadTarget(Row.class, entityType)) { + + ConversionService conversionService = relationalConverter.getConversionService(); + rowMapper = (row, rowMetadata) -> (T) conversionService.convert(row, entityType); + } else if (simpleType) { rowMapper = dataAccessStrategy.getRowMapper(resultType); } else { diff --git a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java index 3529a10ff..fa2e4bc49 100644 --- a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java +++ b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java @@ -21,6 +21,7 @@ import static org.springframework.data.relational.core.query.Criteria.*; import io.r2dbc.spi.Parameters; import io.r2dbc.spi.R2dbcType; +import io.r2dbc.spi.Row; import io.r2dbc.spi.test.MockColumnMetadata; import io.r2dbc.spi.test.MockResult; import io.r2dbc.spi.test.MockRow; @@ -62,6 +63,7 @@ import org.springframework.data.relational.core.query.Criteria; import org.springframework.data.relational.core.query.Query; import org.springframework.data.relational.core.query.Update; import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.data.relational.domain.RowDocument; import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.r2dbc.core.Parameter; @@ -88,7 +90,8 @@ public class R2dbcEntityTemplateUnitTests { client = DatabaseClient.builder().connectionFactory(recorder) .bindMarkers(PostgresDialect.INSTANCE.getBindMarkersFactory()).build(); - R2dbcCustomConversions conversions = R2dbcCustomConversions.of(PostgresDialect.INSTANCE, new MoneyConverter()); + R2dbcCustomConversions conversions = R2dbcCustomConversions.of(PostgresDialect.INSTANCE, new MoneyConverter(), + new RowConverter(), new RowDocumentConverter()); entityTemplate = new R2dbcEntityTemplate(client, PostgresDialect.INSTANCE, new MappingR2dbcConverter(new R2dbcMappingContext(), conversions)); @@ -610,6 +613,42 @@ public class R2dbcEntityTemplateUnitTests { Parameter.from(Parameters.in(R2dbcType.VARCHAR, "$$$"))); } + @Test // GH-1696 + void shouldConsiderRowConverter() { + + MockRowMetadata metadata = MockRowMetadata.builder() + .columnMetadata(MockColumnMetadata.builder().name("foo").type(R2dbcType.INTEGER).build()) + .columnMetadata(MockColumnMetadata.builder().name("bar").type(R2dbcType.VARCHAR).build()).build(); + MockResult result = MockResult.builder().row(MockRow.builder().identified("foo", Object.class, 42) + .identified("bar", String.class, "the-bar").metadata(metadata).build()).build(); + + recorder.addStubbing(s -> s.startsWith("SELECT"), result); + + entityTemplate.select(MyRowToEntityType.class).all().as(StepVerifier::create) // + .assertNext(actual -> { + assertThat(actual.foo).isEqualTo(1); // converter-fixed value + assertThat(actual.bar).isEqualTo("the-bar"); // converted value + }).verifyComplete(); + } + + @Test // GH-1696 + void shouldConsiderRowDocumentConverter() { + + MockRowMetadata metadata = MockRowMetadata.builder() + .columnMetadata(MockColumnMetadata.builder().name("foo").type(R2dbcType.INTEGER).build()) + .columnMetadata(MockColumnMetadata.builder().name("bar").type(R2dbcType.VARCHAR).build()).build(); + MockResult result = MockResult.builder().row(MockRow.builder().identified("foo", Object.class, 42) + .identified("bar", Object.class, "the-bar").metadata(metadata).build()).build(); + + recorder.addStubbing(s -> s.startsWith("SELECT"), result); + + entityTemplate.select(MyRowDocumentToEntityType.class).all().as(StepVerifier::create) // + .assertNext(actual -> { + assertThat(actual.foo).isEqualTo(1); // converter-fixed value + assertThat(actual.bar).isEqualTo("the-bar"); // converted value + }).verifyComplete(); + } + record WithoutId(String name) { } @@ -825,4 +864,30 @@ public class R2dbcEntityTemplateUnitTests { } } + + record MyRowToEntityType(int foo, String bar) { + + } + + static class RowConverter implements Converter { + + @Override + public MyRowToEntityType convert(Row source) { + return new MyRowToEntityType(1, source.get("bar", String.class)); + } + + } + + record MyRowDocumentToEntityType(int foo, String bar) { + + } + + static class RowDocumentConverter implements Converter { + + @Override + public MyRowDocumentToEntityType convert(RowDocument source) { + return new MyRowDocumentToEntityType(1, (String) source.get("bar")); + } + + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java index 9503be81a..2c38f5bff 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java @@ -41,7 +41,16 @@ import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PersistentPropertyPathAccessor; import org.springframework.data.mapping.context.MappingContext; -import org.springframework.data.mapping.model.*; +import org.springframework.data.mapping.model.ConvertingPropertyAccessor; +import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator; +import org.springframework.data.mapping.model.EntityInstantiator; +import org.springframework.data.mapping.model.ParameterValueProvider; +import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; +import org.springframework.data.mapping.model.PropertyValueProvider; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.mapping.model.SpELContext; +import org.springframework.data.mapping.model.SpELExpressionEvaluator; +import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider; import org.springframework.data.projection.EntityProjection; import org.springframework.data.projection.EntityProjectionIntrospector; import org.springframework.data.projection.EntityProjectionIntrospector.ProjectionPredicate; @@ -316,8 +325,8 @@ public class MappingRelationalConverter extends AbstractRelationalConverter impl Class rawType = typeHint.getType(); - if (getConversions().hasCustomReadTarget(documentAccessor.getClass(), rawType)) { - return doConvert(documentAccessor, rawType, typeHint.getType()); + if (getConversions().hasCustomReadTarget(RowDocument.class, rawType)) { + return doConvert(documentAccessor.getDocument(), rawType, typeHint.getType()); } if (RowDocument.class.isAssignableFrom(rawType)) { @@ -1199,8 +1208,7 @@ public class MappingRelationalConverter extends AbstractRelationalConverter impl } } - private record PropertyTranslatingPropertyAccessor( - PersistentPropertyAccessor delegate, + private record PropertyTranslatingPropertyAccessor(PersistentPropertyAccessor delegate, PersistentPropertyTranslator propertyTranslator) implements PersistentPropertyAccessor { static PersistentPropertyAccessor create(PersistentPropertyAccessor delegate,