diff --git a/src/main/java/org/springframework/data/r2dbc/function/DatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/function/DatabaseClient.java index 3116117..e587d07 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DatabaseClient.java @@ -348,8 +348,9 @@ public interface DatabaseClient { * Specify a {@literal null} value to insert. * * @param field must not be {@literal null} or empty. + * @param type must not be {@literal null}. */ - GenericInsertSpec nullValue(String field); + GenericInsertSpec nullValue(String field, Class type); } /** @@ -417,8 +418,9 @@ public interface DatabaseClient { * Bind a {@literal null} value to a parameter identified by its {@code index}. * * @param index + * @param type must not be {@literal null}. */ - S bindNull(int index); + S bindNull(int index, Class type); /** * Bind a non-{@literal null} value to a parameter identified by its {@code name}. @@ -432,8 +434,9 @@ public interface DatabaseClient { * Bind a {@literal null} value to a parameter identified by its {@code name}. * * @param name must not be {@literal null} or empty. + * @param type must not be {@literal null}. */ - S bindNull(String name); + S bindNull(String name, Class type); /** * Bind a bean according to Java {@link java.beans.BeanInfo Beans} using property names. diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java index 5c5adc4..0b4ce1e 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java @@ -36,7 +36,6 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import java.util.function.Function; @@ -53,10 +52,10 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.NullHandling; import org.springframework.data.domain.Sort.Order; import org.springframework.data.r2dbc.UncategorizedR2dbcException; +import org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy.SettableValue; import org.springframework.data.r2dbc.function.connectionfactory.ConnectionProxy; import org.springframework.data.r2dbc.function.convert.ColumnMapRowMapper; import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator; -import org.springframework.data.util.Pair; import org.springframework.jdbc.core.SqlProvider; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -217,24 +216,24 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { return (dae != null ? dae : new UncategorizedR2dbcException(task, sql, ex)); } - private static void doBind(Statement statement, Map> byName, - Map> byIndex) { + private static void doBind(Statement statement, Map byName, + Map byIndex) { byIndex.forEach((i, o) -> { - if (o.isPresent()) { - o.ifPresent(v -> statement.bind(i, v)); + if (o.getValue() != null) { + statement.bind(i, o.getValue()); } else { - statement.bindNull(i, 0); // TODO: What is type? + statement.bindNull(i, o.getType()); } }); byName.forEach((name, o) -> { - if (o.isPresent()) { - o.ifPresent(v -> statement.bind(name, v)); + if (o.getValue() != null) { + statement.bind(name, o.getValue()); } else { - statement.bindNull(name, 0); // TODO: What is type? + statement.bindNull(name, o.getType()); } }); @@ -267,8 +266,8 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { @RequiredArgsConstructor private class GenericExecuteSpecSupport { - final Map> byIndex; - final Map> byName; + final Map byIndex; + final Map byName; final Supplier sqlSupplier; GenericExecuteSpecSupport(Supplier sqlSupplier) { @@ -310,16 +309,16 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { public GenericExecuteSpecSupport bind(int index, Object value) { - Map> byIndex = new LinkedHashMap<>(this.byIndex); - byIndex.put(index, Optional.of(value)); + Map byIndex = new LinkedHashMap<>(this.byIndex); + byIndex.put(index, new SettableValue(index, value, null)); return createInstance(byIndex, this.byName, this.sqlSupplier); } - public GenericExecuteSpecSupport bindNull(int index) { + public GenericExecuteSpecSupport bindNull(int index, Class type) { - Map> byIndex = new LinkedHashMap<>(this.byIndex); - byIndex.put(index, Optional.empty()); + Map byIndex = new LinkedHashMap<>(this.byIndex); + byIndex.put(index, new SettableValue(index, null, type)); return createInstance(byIndex, this.byName, this.sqlSupplier); } @@ -328,24 +327,24 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { Assert.hasText(name, "Parameter name must not be null or empty!"); - Map> byName = new LinkedHashMap<>(this.byName); - byName.put(name, Optional.of(value)); + Map byName = new LinkedHashMap<>(this.byName); + byName.put(name, new SettableValue(name, value, null)); return createInstance(this.byIndex, byName, this.sqlSupplier); } - public GenericExecuteSpecSupport bindNull(String name) { + public GenericExecuteSpecSupport bindNull(String name, Class type) { Assert.hasText(name, "Parameter name must not be null or empty!"); - Map> byName = new LinkedHashMap<>(this.byName); - byName.put(name, Optional.empty()); + Map byName = new LinkedHashMap<>(this.byName); + byName.put(name, new SettableValue(name, null, type)); return createInstance(this.byIndex, byName, this.sqlSupplier); } - protected GenericExecuteSpecSupport createInstance(Map> byIndex, - Map> byName, Supplier sqlSupplier) { + protected GenericExecuteSpecSupport createInstance(Map byIndex, + Map byName, Supplier sqlSupplier) { return new GenericExecuteSpecSupport(byIndex, byName, sqlSupplier); } @@ -362,7 +361,7 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { */ private class DefaultGenericExecuteSpec extends GenericExecuteSpecSupport implements GenericExecuteSpec { - DefaultGenericExecuteSpec(Map> byIndex, Map> byName, + DefaultGenericExecuteSpec(Map byIndex, Map byName, Supplier sqlSupplier) { super(byIndex, byName, sqlSupplier); } @@ -395,8 +394,8 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { } @Override - public DefaultGenericExecuteSpec bindNull(int index) { - return (DefaultGenericExecuteSpec) super.bindNull(index); + public DefaultGenericExecuteSpec bindNull(int index, Class type) { + return (DefaultGenericExecuteSpec) super.bindNull(index, type); } @Override @@ -405,8 +404,8 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { } @Override - public DefaultGenericExecuteSpec bindNull(String name) { - return (DefaultGenericExecuteSpec) super.bindNull(name); + public DefaultGenericExecuteSpec bindNull(String name, Class type) { + return (DefaultGenericExecuteSpec) super.bindNull(name, type); } @Override @@ -415,8 +414,8 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { } @Override - protected GenericExecuteSpecSupport createInstance(Map> byIndex, - Map> byName, Supplier sqlSupplier) { + protected GenericExecuteSpecSupport createInstance(Map byIndex, + Map byName, Supplier sqlSupplier) { return new DefaultGenericExecuteSpec(byIndex, byName, sqlSupplier); } } @@ -430,7 +429,7 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { private final Class typeToRead; private final BiFunction mappingFunction; - DefaultTypedGenericExecuteSpec(Map> byIndex, Map> byName, + DefaultTypedGenericExecuteSpec(Map byIndex, Map byName, Supplier sqlSupplier, Class typeToRead) { super(byIndex, byName, sqlSupplier); @@ -463,8 +462,8 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { } @Override - public DefaultTypedGenericExecuteSpec bindNull(int index) { - return (DefaultTypedGenericExecuteSpec) super.bindNull(index); + public DefaultTypedGenericExecuteSpec bindNull(int index, Class type) { + return (DefaultTypedGenericExecuteSpec) super.bindNull(index, type); } @Override @@ -473,8 +472,8 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { } @Override - public DefaultTypedGenericExecuteSpec bindNull(String name) { - return (DefaultTypedGenericExecuteSpec) super.bindNull(name); + public DefaultTypedGenericExecuteSpec bindNull(String name, Class type) { + return (DefaultTypedGenericExecuteSpec) super.bindNull(name, type); } @Override @@ -483,8 +482,8 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { } @Override - protected DefaultTypedGenericExecuteSpec createInstance(Map> byIndex, - Map> byName, Supplier sqlSupplier) { + protected DefaultTypedGenericExecuteSpec createInstance(Map byIndex, + Map byName, Supplier sqlSupplier) { return new DefaultTypedGenericExecuteSpec<>(byIndex, byName, sqlSupplier, typeToRead); } } @@ -800,26 +799,26 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { class DefaultGenericInsertSpec implements GenericInsertSpec { private final String table; - private final Map> byName; + private final Map byName; @Override public GenericInsertSpec value(String field, Object value) { Assert.notNull(field, "Field must not be null!"); - Map> byName = new LinkedHashMap<>(this.byName); - byName.put(field, Optional.of(value)); + Map byName = new LinkedHashMap<>(this.byName); + byName.put(field, new SettableValue(field, value, null)); return new DefaultGenericInsertSpec(this.table, byName); } @Override - public GenericInsertSpec nullValue(String field) { + public GenericInsertSpec nullValue(String field, Class type) { Assert.notNull(field, "Field must not be null!"); - Map> byName = new LinkedHashMap<>(this.byName); - byName.put(field, Optional.empty()); + Map byName = new LinkedHashMap<>(this.byName); + byName.put(field, new SettableValue(field, null, type)); return new DefaultGenericInsertSpec(this.table, byName); } @@ -878,12 +877,12 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { AtomicInteger index = new AtomicInteger(); - for (Optional o : byName.values()) { + for (SettableValue value : byName.values()) { - if (o.isPresent()) { - o.ifPresent(v -> statement.bind(index.getAndIncrement(), v)); + if (value.getValue() != null) { + statement.bind(index.getAndIncrement(), value.getValue()); } else { - statement.bindNull("$" + (index.getAndIncrement() + 1), 0); // TODO: What is type? + statement.bindNull("$" + (index.getAndIncrement() + 1), value.getType()); } } } @@ -944,8 +943,9 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { StringBuilder builder = new StringBuilder(); - List> insertValues = dataAccessStrategy.getInsert(toInsert); - String fieldNames = insertValues.stream().map(Pair::getFirst).collect(Collectors.joining(",")); + List insertValues = dataAccessStrategy.getInsert(toInsert); + String fieldNames = insertValues.stream().map(SettableValue::getIdentifier).map(Object::toString) + .collect(Collectors.joining(",")); String placeholders = IntStream.range(0, insertValues.size()).mapToObj(i -> "$" + (i + 1)) .collect(Collectors.joining(",")); @@ -964,12 +964,12 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { AtomicInteger index = new AtomicInteger(); - for (Pair pair : insertValues) { + for (SettableValue settable : insertValues) { - if (pair.getSecond() != null) { // TODO: Better type to transport null values. - statement.bind(index.getAndIncrement(), pair.getSecond()); + if (settable.getValue() != null) { + statement.bind(index.getAndIncrement(), settable.getValue()); } else { - statement.bindNull("$" + (index.getAndIncrement() + 1), 0); // TODO: What is type? + statement.bindNull("$" + (index.getAndIncrement() + 1), settable.getType()); } } diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java index c6d52ca..332bcb4 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java @@ -24,16 +24,17 @@ import java.util.List; import java.util.function.BiFunction; import java.util.stream.Collectors; -import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.r2dbc.function.convert.EntityRowMapper; +import org.springframework.data.relational.core.conversion.BasicRelationalConverter; +import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.data.util.Pair; import org.springframework.data.util.StreamUtils; +import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; /** @@ -41,22 +42,20 @@ import org.springframework.util.ClassUtils; */ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStrategy { - private final RelationalMappingContext mappingContext; - private final EntityInstantiators instantiators; + private final RelationalConverter relationalConverter; public DefaultReactiveDataAccessStrategy() { - this(new RelationalMappingContext(), new EntityInstantiators()); + this(new BasicRelationalConverter(new RelationalMappingContext())); } - public DefaultReactiveDataAccessStrategy(RelationalMappingContext mappingContext, EntityInstantiators instantiators) { - this.mappingContext = mappingContext; - this.instantiators = instantiators; + public DefaultReactiveDataAccessStrategy(RelationalConverter converter) { + this.relationalConverter = converter; } @Override public List getAllFields(Class typeToRead) { - RelationalPersistentEntity persistentEntity = mappingContext.getPersistentEntity(typeToRead); + RelationalPersistentEntity persistentEntity = getPersistentEntity(typeToRead); if (persistentEntity == null) { return Collections.singletonList("*"); @@ -68,14 +67,14 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra } @Override - public List> getInsert(Object object) { + public List getInsert(Object object) { Class userClass = ClassUtils.getUserClass(object); - RelationalPersistentEntity entity = mappingContext.getRequiredPersistentEntity(userClass); + RelationalPersistentEntity entity = getRequiredPersistentEntity(userClass); PersistentPropertyAccessor propertyAccessor = entity.getPropertyAccessor(object); - List> values = new ArrayList<>(); + List values = new ArrayList<>(); for (RelationalPersistentProperty property : entity) { @@ -85,7 +84,7 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra continue; } - values.add(Pair.of(property.getColumnName(), value)); + values.add(new SettableValue(property.getColumnName(), value, property.getType())); } return values; @@ -94,7 +93,7 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra @Override public Sort getMappedSort(Class typeToRead, Sort sort) { - RelationalPersistentEntity entity = mappingContext.getPersistentEntity(typeToRead); + RelationalPersistentEntity entity = getPersistentEntity(typeToRead); if (entity == null) { return sort; } @@ -117,12 +116,21 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra @Override public BiFunction getRowMapper(Class typeToRead) { - return new EntityRowMapper((RelationalPersistentEntity) mappingContext.getRequiredPersistentEntity(typeToRead), - instantiators, mappingContext); + return new EntityRowMapper((RelationalPersistentEntity) getRequiredPersistentEntity(typeToRead), + relationalConverter); } @Override public String getTableName(Class type) { - return mappingContext.getRequiredPersistentEntity(type).getTableName(); + return getRequiredPersistentEntity(type).getTableName(); + } + + private RelationalPersistentEntity getRequiredPersistentEntity(Class typeToRead) { + return relationalConverter.getMappingContext().getRequiredPersistentEntity(typeToRead); + } + + @Nullable + private RelationalPersistentEntity getPersistentEntity(Class typeToRead) { + return relationalConverter.getMappingContext().getPersistentEntity(typeToRead); } } diff --git a/src/main/java/org/springframework/data/r2dbc/function/ReactiveDataAccessStrategy.java b/src/main/java/org/springframework/data/r2dbc/function/ReactiveDataAccessStrategy.java index 566c2ae..76711a5 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/ReactiveDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/r2dbc/function/ReactiveDataAccessStrategy.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.function.BiFunction; import org.springframework.data.domain.Sort; -import org.springframework.data.util.Pair; +import org.springframework.lang.Nullable; /** * @author Mark Paluch @@ -31,7 +31,7 @@ public interface ReactiveDataAccessStrategy { List getAllFields(Class typeToRead); - List> getInsert(Object object); + List getInsert(Object object); Sort getMappedSort(Class typeToRead, Sort sort); @@ -39,4 +39,55 @@ public interface ReactiveDataAccessStrategy { BiFunction getRowMapper(Class typeToRead); String getTableName(Class type); + + /** + * A database value that can be set in a statement. + */ + class SettableValue { + + private final Object identifier; + private final @Nullable Object value; + private final Class type; + + /** + * Create a {@link SettableValue} using an integer index. + * + * @param index + * @param value + * @param type + */ + public SettableValue(int index, @Nullable Object value, Class type) { + + this.identifier = index; + this.value = value; + this.type = type; + } + + /** + * Create a {@link SettableValue} using a {@link String} identifier. + * + * @param identifier + * @param value + * @param type + */ + public SettableValue(String identifier, @Nullable Object value, Class type) { + + this.identifier = identifier; + this.value = value; + this.type = type; + } + + public Object getIdentifier() { + return identifier; + } + + @Nullable + public Object getValue() { + return value; + } + + public Class getType() { + return type; + } + } } diff --git a/src/main/java/org/springframework/data/r2dbc/function/convert/ColumnMapRowMapper.java b/src/main/java/org/springframework/data/r2dbc/function/convert/ColumnMapRowMapper.java index acfa37d..d7acd3c 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/convert/ColumnMapRowMapper.java +++ b/src/main/java/org/springframework/data/r2dbc/function/convert/ColumnMapRowMapper.java @@ -89,7 +89,7 @@ public class ColumnMapRowMapper implements BiFunction - * The default implementation uses the {@link Row#get(Object, Class)} method. + * The default implementation uses the {@link Row#get(Object)} method. * * @param row is the {@link Row} holding the data. * @param index is the column index. @@ -97,6 +97,6 @@ public class ColumnMapRowMapper implements BiFunction implements BiFunction { private final RelationalPersistentEntity entity; - private final EntityInstantiators entityInstantiators; - private final ConversionService conversions; - private final MappingContext, RelationalPersistentProperty> context; + private final RelationalConverter converter; - public EntityRowMapper(RelationalPersistentEntity entity, EntityInstantiators entityInstantiators, - RelationalMappingContext context) { + public EntityRowMapper(RelationalPersistentEntity entity, RelationalConverter converter) { this.entity = entity; - this.entityInstantiators = entityInstantiators; - this.conversions = context.getConversions(); - this.context = context; + this.converter = converter; } @Override @@ -65,7 +57,7 @@ public class EntityRowMapper implements BiFunction { T result = createInstance(row, "", entity); ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(result), - conversions); + converter.getConversionService()); for (RelationalPersistentProperty property : entity) { @@ -103,23 +95,19 @@ public class EntityRowMapper implements BiFunction { return readEntityFrom(row, property); } - return row.get(prefix + property.getColumnName(), getType(property)); + return row.get(prefix + property.getColumnName()); } catch (Exception o_O) { throw new MappingException(String.format("Could not read property %s from result set!", property), o_O); } } - private static Class getType(RelationalPersistentProperty property) { - return ClassUtils.resolvePrimitiveIfNecessary(property.getActualType()); - } - private S readEntityFrom(Row row, PersistentProperty property) { String prefix = property.getName() + "_"; @SuppressWarnings("unchecked") - RelationalPersistentEntity entity = (RelationalPersistentEntity) context + RelationalPersistentEntity entity = (RelationalPersistentEntity) converter.getMappingContext() .getRequiredPersistentEntity(property.getActualType()); if (readFrom(row, entity.getRequiredIdProperty(), prefix) == null) { @@ -129,7 +117,8 @@ public class EntityRowMapper implements BiFunction { S instance = createInstance(row, prefix, entity); PersistentPropertyAccessor accessor = entity.getPropertyAccessor(instance); - ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(accessor, conversions); + ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(accessor, + converter.getConversionService()); for (RelationalPersistentProperty p : entity) { if (!entity.isConstructorArgument(property)) { @@ -142,8 +131,10 @@ public class EntityRowMapper implements BiFunction { private S createInstance(Row row, String prefix, RelationalPersistentEntity entity) { - return entityInstantiators.getInstantiatorFor(entity).createInstance(entity, - new RowParameterValueProvider(row, entity, conversions, prefix)); + RowParameterValueProvider rowParameterValueProvider = new RowParameterValueProvider(row, entity, + converter.getConversionService(), prefix); + + return converter.createInstance(entity, rowParameterValueProvider::getParameterValue); } @RequiredArgsConstructor @@ -164,7 +155,7 @@ public class EntityRowMapper implements BiFunction { String column = prefix + entity.getRequiredPersistentProperty(parameter.getName()).getColumnName(); try { - return conversionService.convert(resultSet.get(column, parameter.getType().getType()), + return conversionService.convert(resultSet.get(column), parameter.getType().getType()); } catch (Exception o_O) { throw new MappingException(String.format("Couldn't read column %s from Row.", column), o_O); diff --git a/src/main/java/org/springframework/data/r2dbc/function/convert/MappingR2dbcConverter.java b/src/main/java/org/springframework/data/r2dbc/function/convert/MappingR2dbcConverter.java index 6f712d0..21fb76c 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/convert/MappingR2dbcConverter.java +++ b/src/main/java/org/springframework/data/r2dbc/function/convert/MappingR2dbcConverter.java @@ -23,8 +23,11 @@ import java.util.Map; import java.util.Optional; import java.util.function.BiFunction; +import org.springframework.core.convert.ConversionService; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy.SettableValue; +import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.util.Assert; @@ -37,11 +40,10 @@ import org.springframework.util.ClassUtils; */ public class MappingR2dbcConverter { - private final MappingContext, RelationalPersistentProperty> mappingContext; + private final RelationalConverter relationalConverter; - public MappingR2dbcConverter( - MappingContext, RelationalPersistentProperty> mappingContext) { - this.mappingContext = mappingContext; + public MappingR2dbcConverter(RelationalConverter converter) { + this.relationalConverter = converter; } /** @@ -51,19 +53,20 @@ public class MappingR2dbcConverter { * @param object must not be {@literal null}. * @return */ - public Map> getFieldsToUpdate(Object object) { + public Map getFieldsToUpdate(Object object) { Assert.notNull(object, "Entity object must not be null!"); Class userClass = ClassUtils.getUserClass(object); - RelationalPersistentEntity entity = mappingContext.getRequiredPersistentEntity(userClass); + RelationalPersistentEntity entity = getMappingContext().getRequiredPersistentEntity(userClass); - Map> update = new LinkedHashMap<>(); + Map update = new LinkedHashMap<>(); PersistentPropertyAccessor propertyAccessor = entity.getPropertyAccessor(object); for (RelationalPersistentProperty property : entity) { - update.put(property.getColumnName(), Optional.ofNullable(propertyAccessor.getProperty(property))); + update.put(property.getColumnName(), + new SettableValue(property.getColumnName(), propertyAccessor.getProperty(property), property.getType())); } return update; @@ -82,7 +85,7 @@ public class MappingR2dbcConverter { Assert.notNull(object, "Entity object must not be null!"); Class userClass = ClassUtils.getUserClass(object); - RelationalPersistentEntity entity = mappingContext.getRequiredPersistentEntity(userClass); + RelationalPersistentEntity entity = getMappingContext().getRequiredPersistentEntity(userClass); return (row, metadata) -> { @@ -91,7 +94,11 @@ public class MappingR2dbcConverter { if (propertyAccessor.getProperty(idProperty) == null) { - propertyAccessor.setProperty(idProperty, row.get(idProperty.getColumnName(), idProperty.getColumnType())); + ConversionService conversionService = relationalConverter.getConversionService(); + Object value = row.get(idProperty.getColumnName()); + + propertyAccessor.setProperty(idProperty, conversionService.convert(value, idProperty.getType())); + return (T) propertyAccessor.getBean(); } @@ -99,7 +106,8 @@ public class MappingR2dbcConverter { }; } - public MappingContext, RelationalPersistentProperty> getMappingContext() { - return mappingContext; + public MappingContext, ? extends RelationalPersistentProperty> getMappingContext() { + return relationalConverter.getMappingContext(); } } + diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java index f0f45e9..41c5339 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java @@ -64,7 +64,7 @@ interface R2dbcQueryExecution { final class ResultProcessingConverter implements Converter { private final @NonNull ResultProcessor processor; - private final @NonNull MappingContext, RelationalPersistentProperty> mappingContext; + private final @NonNull MappingContext, ? extends RelationalPersistentProperty> mappingContext; private final @NonNull EntityInstantiators instantiators; /* (non-Javadoc) diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethod.java b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethod.java index 3ebf201..48f7fd5 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethod.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethod.java @@ -56,7 +56,7 @@ public class R2dbcQueryMethod extends QueryMethod { private static final ClassTypeInformation SLICE_TYPE = ClassTypeInformation.from(Slice.class); private final Method method; - private final MappingContext, RelationalPersistentProperty> mappingContext; + private final MappingContext, ? extends RelationalPersistentProperty> mappingContext; private final Optional query; private @Nullable RelationalEntityMetadata metadata; @@ -70,7 +70,7 @@ public class R2dbcQueryMethod extends QueryMethod { * @param mappingContext must not be {@literal null}. */ public R2dbcQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory projectionFactory, - MappingContext, RelationalPersistentProperty> mappingContext) { + MappingContext, ? extends RelationalPersistentProperty> mappingContext) { super(method, metadata, projectionFactory); diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java index ff388dc..3ff5291 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java @@ -20,6 +20,8 @@ import org.springframework.data.r2dbc.function.DatabaseClient; import org.springframework.data.r2dbc.function.DatabaseClient.BindSpec; import org.springframework.data.r2dbc.function.convert.MappingR2dbcConverter; import org.springframework.data.relational.repository.query.RelationalParameterAccessor; +import org.springframework.data.repository.query.Parameter; +import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; @@ -88,12 +90,17 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery { T bindSpecToUse = bindSpec; // TODO: Encapsulate PostgreSQL-specific bindings + + Parameters bindableParameters = accessor.getBindableParameters(); + int index = 1; for (Object value : accessor.getValues()) { + Parameter bindableParameter = bindableParameters.getBindableParameter(index - 1); + if (value == null) { if (accessor.hasBindableNullValue()) { - bindSpecToUse = bindSpecToUse.bindNull("$" + (index++)); + bindSpecToUse = bindSpecToUse.bindNull("$" + (index++), bindableParameter.getType()); } } else { bindSpecToUse = bindSpecToUse.bind("$" + (index++), value); diff --git a/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java b/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java index 35b5fd2..4d60545 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java @@ -28,6 +28,7 @@ import org.springframework.data.r2dbc.function.convert.MappingR2dbcConverter; import org.springframework.data.r2dbc.repository.R2dbcRepository; import org.springframework.data.r2dbc.repository.query.R2dbcQueryMethod; import org.springframework.data.r2dbc.repository.query.StringBasedR2dbcQuery; +import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.repository.query.RelationalEntityInformation; @@ -71,7 +72,7 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport { this.databaseClient = databaseClient; this.mappingContext = mappingContext; - this.converter = new MappingR2dbcConverter(mappingContext); + this.converter = new MappingR2dbcConverter(new BasicRelationalConverter(mappingContext)); } /* diff --git a/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java b/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java index 943c43a..03537b9 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java @@ -22,7 +22,6 @@ import reactor.core.publisher.Mono; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -31,6 +30,7 @@ import org.springframework.data.r2dbc.function.DatabaseClient; import org.springframework.data.r2dbc.function.DatabaseClient.BindSpec; import org.springframework.data.r2dbc.function.DatabaseClient.GenericExecuteSpec; import org.springframework.data.r2dbc.function.FetchSpec; +import org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy.SettableValue; import org.springframework.data.r2dbc.function.convert.MappingR2dbcConverter; import org.springframework.data.relational.repository.query.RelationalEntityInformation; import org.springframework.data.repository.reactive.ReactiveCrudRepository; @@ -68,7 +68,7 @@ public class SimpleR2dbcRepository implements ReactiveCrudRepository> fields = converter.getFieldsToUpdate(objectToSave); + Map fields = converter.getFieldsToUpdate(objectToSave); String setClause = getSetClause(fields); @@ -77,13 +77,13 @@ public class SimpleR2dbcRepository implements ReactiveCrudRepository setValue : fields.values()) { + for (SettableValue setValue : fields.values()) { - Object value = setValue.orElse(null); + Object value = setValue.getValue(); if (value != null) { exec = exec.bind(index++, value); } else { - exec = exec.bindNull(index++); + exec = exec.bindNull(index++, setValue.getType()); } } @@ -93,7 +93,7 @@ public class SimpleR2dbcRepository implements ReactiveCrudRepository> fields) { + private static String getSetClause(Map fields) { StringBuilder setClause = new StringBuilder(); diff --git a/src/main/java/org/springframework/data/relational/repository/query/DtoInstantiatingConverter.java b/src/main/java/org/springframework/data/relational/repository/query/DtoInstantiatingConverter.java index a40eeda..6299e38 100644 --- a/src/main/java/org/springframework/data/relational/repository/query/DtoInstantiatingConverter.java +++ b/src/main/java/org/springframework/data/relational/repository/query/DtoInstantiatingConverter.java @@ -49,7 +49,7 @@ public class DtoInstantiatingConverter implements Converter { * @param instantiators must not be {@literal null}. */ public DtoInstantiatingConverter(Class dtoType, - MappingContext, RelationalPersistentProperty> context, + MappingContext, ? extends RelationalPersistentProperty> context, EntityInstantiators instantiator) { Assert.notNull(dtoType, "DTO type must not be null!"); diff --git a/src/main/java/org/springframework/data/relational/repository/query/RelationalParameterAccessor.java b/src/main/java/org/springframework/data/relational/repository/query/RelationalParameterAccessor.java index 50f8aae..5212356 100644 --- a/src/main/java/org/springframework/data/relational/repository/query/RelationalParameterAccessor.java +++ b/src/main/java/org/springframework/data/relational/repository/query/RelationalParameterAccessor.java @@ -16,6 +16,7 @@ package org.springframework.data.relational.repository.query; import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.Parameters; /** * JDBC-specific {@link ParameterAccessor}. @@ -28,4 +29,9 @@ public interface RelationalParameterAccessor extends ParameterAccessor { * Returns the raw parameter values of the underlying query method. */ Object[] getValues(); + + /** + * @return the bindable parameters. + */ + Parameters getBindableParameters(); } diff --git a/src/main/java/org/springframework/data/relational/repository/query/RelationalParametersParameterAccessor.java b/src/main/java/org/springframework/data/relational/repository/query/RelationalParametersParameterAccessor.java index 62bfc09..c62c7cb 100644 --- a/src/main/java/org/springframework/data/relational/repository/query/RelationalParametersParameterAccessor.java +++ b/src/main/java/org/springframework/data/relational/repository/query/RelationalParametersParameterAccessor.java @@ -18,6 +18,7 @@ package org.springframework.data.relational.repository.query; import java.util.Arrays; import java.util.List; +import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.QueryMethod; @@ -50,4 +51,12 @@ public class RelationalParametersParameterAccessor extends ParametersParameterAc public Object[] getValues() { return values.toArray(); } + + /* (non-Javadoc) + * @see org.springframework.data.jdbc.repository.query.RelationalParameterAccessor#getBindableParameters() + */ + @Override + public Parameters getBindableParameters() { + return getParameters().getBindableParameters(); + } } diff --git a/src/test/java/org/springframework/data/r2dbc/function/DatabaseClientIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/function/DatabaseClientIntegrationTests.java index 77cdac6..8ab0fda 100644 --- a/src/test/java/org/springframework/data/r2dbc/function/DatabaseClientIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/function/DatabaseClientIntegrationTests.java @@ -67,7 +67,7 @@ public class DatabaseClientIntegrationTests extends R2dbcIntegrationTestSupport databaseClient.execute().sql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)") // .bind(0, 42055) // .bind(1, "SCHAUFELRADBAGGER") // - .bindNull("$3") // + .bindNull("$3", Integer.class) // .fetch().rowsUpdated() // .as(StepVerifier::create) // .expectNext(1) // @@ -86,7 +86,7 @@ public class DatabaseClientIntegrationTests extends R2dbcIntegrationTestSupport databaseClient.execute().sql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)") // .bind(0, 42055) // .bind(1, "SCHAUFELRADBAGGER") // - .bindNull("$3") // + .bindNull("$3", Integer.class) // .fetch().rowsUpdated() // .as(StepVerifier::create) // .expectErrorSatisfies(exception -> { @@ -125,7 +125,7 @@ public class DatabaseClientIntegrationTests extends R2dbcIntegrationTestSupport databaseClient.insert().into("legoset")// .value("id", 42055) // .value("name", "SCHAUFELRADBAGGER") // - .nullValue("manual") // + .nullValue("manual", Integer.class) // .exchange() // .flatMapMany(it -> it.extract((r, m) -> r.get("id", Integer.class)).all()) // .as(StepVerifier::create) // @@ -142,7 +142,7 @@ public class DatabaseClientIntegrationTests extends R2dbcIntegrationTestSupport databaseClient.insert().into("legoset")// .value("id", 42055) // .value("name", "SCHAUFELRADBAGGER") // - .nullValue("manual") // + .nullValue("manual", Integer.class) // .then() // .as(StepVerifier::create) // .verifyComplete(); diff --git a/src/test/java/org/springframework/data/r2dbc/function/TransactionalDatabaseClientIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/function/TransactionalDatabaseClientIntegrationTests.java index ade4922..6a3d19c 100644 --- a/src/test/java/org/springframework/data/r2dbc/function/TransactionalDatabaseClientIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/function/TransactionalDatabaseClientIntegrationTests.java @@ -71,7 +71,7 @@ public class TransactionalDatabaseClientIntegrationTests extends R2dbcIntegratio return db.execute().sql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)") // .bind(0, 42055) // .bind(1, "SCHAUFELRADBAGGER") // - .bindNull("$3") // + .bindNull("$3", Integer.class) // .fetch().rowsUpdated(); }); @@ -91,7 +91,7 @@ public class TransactionalDatabaseClientIntegrationTests extends R2dbcIntegratio .sql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)") // .bind(0, 42055) // .bind(1, "SCHAUFELRADBAGGER") // - .bindNull("$3") // + .bindNull("$3", Integer.class) // .fetch().rowsUpdated(); integerFlux.as(StepVerifier::create) // @@ -147,7 +147,7 @@ public class TransactionalDatabaseClientIntegrationTests extends R2dbcIntegratio return db.execute().sql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)") // .bind(0, 42055) // .bind(1, "SCHAUFELRADBAGGER") // - .bindNull("$3") // + .bindNull("$3", Integer.class) // .fetch().rowsUpdated().then(Mono.error(new IllegalStateException("failed"))); }); diff --git a/src/test/java/org/springframework/data/r2dbc/repository/R2dbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/R2dbcRepositoryIntegrationTests.java index cc22129..4dde9d5 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/R2dbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/R2dbcRepositoryIntegrationTests.java @@ -33,13 +33,13 @@ import java.util.Map; import org.junit.Before; import org.junit.Test; import org.springframework.data.annotation.Id; -import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.jdbc.repository.query.Query; import org.springframework.data.jdbc.testing.R2dbcIntegrationTestSupport; import org.springframework.data.r2dbc.function.DatabaseClient; import org.springframework.data.r2dbc.function.DefaultReactiveDataAccessStrategy; import org.springframework.data.r2dbc.function.TransactionalDatabaseClient; import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory; +import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.repository.reactive.ReactiveCrudRepository; @@ -66,7 +66,8 @@ public class R2dbcRepositoryIntegrationTests extends R2dbcIntegrationTestSupport this.connectionFactory = createConnectionFactory(); this.databaseClient = DatabaseClient.builder().connectionFactory(connectionFactory) - .dataAccessStrategy(new DefaultReactiveDataAccessStrategy(mappingContext, new EntityInstantiators())).build(); + .dataAccessStrategy(new DefaultReactiveDataAccessStrategy(new BasicRelationalConverter(mappingContext))) + .build(); this.jdbc = createJdbcTemplate(createDataSource()); @@ -136,7 +137,8 @@ public class R2dbcRepositoryIntegrationTests extends R2dbcIntegrationTestSupport public void shouldInsertItemsTransactional() { TransactionalDatabaseClient client = TransactionalDatabaseClient.builder().connectionFactory(connectionFactory) - .dataAccessStrategy(new DefaultReactiveDataAccessStrategy(mappingContext, new EntityInstantiators())).build(); + .dataAccessStrategy(new DefaultReactiveDataAccessStrategy(new BasicRelationalConverter(mappingContext))) + .build(); LegoSetRepository transactionalRepository = new R2dbcRepositoryFactory(client, mappingContext) .getRepository(LegoSetRepository.class); diff --git a/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java b/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java index afcebfa..c697b43 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java @@ -16,7 +16,8 @@ package org.springframework.data.r2dbc.repository.query; import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; import java.lang.reflect.Method; @@ -32,6 +33,7 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.r2dbc.function.DatabaseClient; import org.springframework.data.r2dbc.function.DatabaseClient.GenericExecuteSpec; import org.springframework.data.r2dbc.function.convert.MappingR2dbcConverter; +import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.RepositoryMetadata; @@ -63,7 +65,7 @@ public class StringBasedR2dbcQueryUnitTests { public void setUp() { this.mappingContext = new RelationalMappingContext(); - this.converter = new MappingR2dbcConverter(this.mappingContext); + this.converter = new MappingR2dbcConverter(new BasicRelationalConverter(this.mappingContext)); this.metadata = AbstractRepositoryMetadata.getMetadata(SampleRepository.class); this.factory = new SpelAwareProxyProjectionFactory(); diff --git a/src/test/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepositoryIntegrationTests.java index 8d9b49d..b5e2dfd 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepositoryIntegrationTests.java @@ -33,11 +33,11 @@ import java.util.Map; import org.junit.Before; import org.junit.Test; import org.springframework.data.annotation.Id; -import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.jdbc.testing.R2dbcIntegrationTestSupport; import org.springframework.data.r2dbc.function.DatabaseClient; import org.springframework.data.r2dbc.function.DefaultReactiveDataAccessStrategy; import org.springframework.data.r2dbc.function.convert.MappingR2dbcConverter; +import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.Table; @@ -66,13 +66,14 @@ public class SimpleR2dbcRepositoryIntegrationTests extends R2dbcIntegrationTestS this.connectionFactory = createConnectionFactory(); this.databaseClient = DatabaseClient.builder().connectionFactory(connectionFactory) - .dataAccessStrategy(new DefaultReactiveDataAccessStrategy(mappingContext, new EntityInstantiators())).build(); + .dataAccessStrategy(new DefaultReactiveDataAccessStrategy(new BasicRelationalConverter(mappingContext))) + .build(); RelationalEntityInformation entityInformation = new MappingRelationalEntityInformation<>( (RelationalPersistentEntity) mappingContext.getRequiredPersistentEntity(LegoSet.class)); this.repository = new SimpleR2dbcRepository<>(entityInformation, databaseClient, - new MappingR2dbcConverter(mappingContext)); + new MappingR2dbcConverter(new BasicRelationalConverter(mappingContext))); this.jdbc = createJdbcTemplate(createDataSource());