diff --git a/pom.xml b/pom.xml
index 9ff59187..5e5eb075 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
org.springframework.data
spring-data-r2dbc
- 1.0.0.BUILD-SNAPSHOT
+ 1.0.0.gh-30-SNAPSHOT
Spring Data R2DBC
Spring Data module for R2DBC.
diff --git a/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java b/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java
index e6b11e88..57c1e3c9 100644
--- a/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java
+++ b/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java
@@ -17,15 +17,20 @@
import io.r2dbc.spi.ConnectionFactory;
+import java.util.Collections;
import java.util.Optional;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.data.convert.CustomConversions;
+import org.springframework.data.convert.CustomConversions.StoreConversions;
import org.springframework.data.r2dbc.dialect.Database;
import org.springframework.data.r2dbc.dialect.Dialect;
import org.springframework.data.r2dbc.function.DatabaseClient;
import org.springframework.data.r2dbc.function.DefaultReactiveDataAccessStrategy;
import org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy;
+import org.springframework.data.r2dbc.function.convert.R2dbcCustomConversions;
import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator;
import org.springframework.data.r2dbc.support.SqlErrorCodeR2dbcExceptionTranslator;
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
@@ -95,33 +100,59 @@ public DatabaseClient databaseClient(ReactiveDataAccessStrategy dataAccessStrate
* Register a {@link RelationalMappingContext} and apply an optional {@link NamingStrategy}.
*
* @param namingStrategy optional {@link NamingStrategy}. Use {@link NamingStrategy#INSTANCE} as fallback.
+ * @param r2dbcCustomConversions customized R2DBC conversions.
* @return must not be {@literal null}.
* @throws IllegalArgumentException if any of the required args is {@literal null}.
*/
@Bean
- public RelationalMappingContext r2dbcMappingContext(Optional namingStrategy) {
+ public RelationalMappingContext r2dbcMappingContext(Optional namingStrategy,
+ R2dbcCustomConversions r2dbcCustomConversions) {
Assert.notNull(namingStrategy, "NamingStrategy must not be null!");
- return new RelationalMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE));
+ RelationalMappingContext relationalMappingContext = new RelationalMappingContext(
+ namingStrategy.orElse(NamingStrategy.INSTANCE));
+ relationalMappingContext.setSimpleTypeHolder(r2dbcCustomConversions.getSimpleTypeHolder());
+
+ return relationalMappingContext;
}
/**
- * Creates a {@link ReactiveDataAccessStrategy} using the configured {@link #r2dbcMappingContext(Optional)
+ * Creates a {@link ReactiveDataAccessStrategy} using the configured {@link #r2dbcMappingContext(Optional, R2dbcCustomConversions)}
* RelationalMappingContext}.
*
* @param mappingContext the configured {@link RelationalMappingContext}.
+ * @param r2dbcCustomConversions customized R2DBC conversions.
* @return must not be {@literal null}.
- * @see #r2dbcMappingContext(Optional)
+ * @see #r2dbcMappingContext(Optional, R2dbcCustomConversions)
* @see #getDialect(ConnectionFactory)
* @throws IllegalArgumentException if any of the {@literal mappingContext} is {@literal null}.
*/
@Bean
- public ReactiveDataAccessStrategy reactiveDataAccessStrategy(RelationalMappingContext mappingContext) {
+ public ReactiveDataAccessStrategy reactiveDataAccessStrategy(RelationalMappingContext mappingContext,
+ R2dbcCustomConversions r2dbcCustomConversions) {
Assert.notNull(mappingContext, "MappingContext must not be null!");
- return new DefaultReactiveDataAccessStrategy(getDialect(connectionFactory()),
- new BasicRelationalConverter(mappingContext));
+
+ BasicRelationalConverter converter = new BasicRelationalConverter(mappingContext, r2dbcCustomConversions);
+
+ return new DefaultReactiveDataAccessStrategy(getDialect(connectionFactory()), converter);
+ }
+
+ /**
+ * Register custom {@link Converter}s in a {@link CustomConversions} object if required. These
+ * {@link CustomConversions} will be registered with the {@link BasicRelationalConverter} and
+ * {@link #r2dbcMappingContext(Optional, R2dbcCustomConversions)}. Returns an empty {@link R2dbcCustomConversions}
+ * instance by default.
+ *
+ * @return must not be {@literal null}.
+ */
+ @Bean
+ public R2dbcCustomConversions r2dbcCustomConversions() {
+
+ Dialect dialect = getDialect(connectionFactory());
+ StoreConversions storeConversions = StoreConversions.of(dialect.getSimpleTypeHolder());
+ return new R2dbcCustomConversions(storeConversions, Collections.emptyList());
}
/**
diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/ArrayColumns.java b/src/main/java/org/springframework/data/r2dbc/dialect/ArrayColumns.java
new file mode 100644
index 00000000..5259af22
--- /dev/null
+++ b/src/main/java/org/springframework/data/r2dbc/dialect/ArrayColumns.java
@@ -0,0 +1,53 @@
+package org.springframework.data.r2dbc.dialect;
+
+/**
+ * Interface declaring methods that express how a dialect supports array-typed columns.
+ *
+ * @author Mark Paluch
+ */
+public interface ArrayColumns {
+
+ /**
+ * Returns {@literal true} if the dialect supports array-typed columns.
+ *
+ * @return {@literal true} if the dialect supports array-typed columns.
+ */
+ boolean isSupported();
+
+ /**
+ * Translate the {@link Class user type} of an array into the dialect-specific type. This method considers only the
+ * component type.
+ *
+ * @param userType component type of the array.
+ * @return the dialect-supported array type.
+ * @throws UnsupportedOperationException if array typed columns are not supported.
+ * @throws IllegalArgumentException if the {@code userType} is not a supported array type.
+ */
+ Class> getArrayType(Class> userType);
+
+ /**
+ * Default {@link ArrayColumns} implementation for dialects that do not support array-typed columns.
+ */
+ enum Unsupported implements ArrayColumns {
+
+ INSTANCE;
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.ArrayColumns#isSupported()
+ */
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.ArrayColumns#getArrayType(java.lang.Class)
+ */
+ @Override
+ public Class> getArrayType(Class> userType) {
+ throw new UnsupportedOperationException("Array types not supported");
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/Dialect.java b/src/main/java/org/springframework/data/r2dbc/dialect/Dialect.java
index 3371d623..e909f722 100644
--- a/src/main/java/org/springframework/data/r2dbc/dialect/Dialect.java
+++ b/src/main/java/org/springframework/data/r2dbc/dialect/Dialect.java
@@ -1,5 +1,12 @@
package org.springframework.data.r2dbc.dialect;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+
+import org.springframework.data.mapping.model.SimpleTypeHolder;
+import org.springframework.data.r2dbc.dialect.ArrayColumns.Unsupported;
+
/**
* Represents a dialect that is implemented by a particular database.
*
@@ -26,10 +33,40 @@ public interface Dialect {
@Deprecated
String generatedKeysClause();
+ /**
+ * Return a collection of types that are natively supported by this database/driver. Defaults to
+ * {@link Collections#emptySet()}.
+ *
+ * @return a collection of types that are natively supported by this database/driver. Defaults to
+ * {@link Collections#emptySet()}.
+ */
+ default Collection extends Class>> getSimpleTypes() {
+ return Collections.emptySet();
+ }
+
+ /**
+ * Return the {@link SimpleTypeHolder} for this dialect.
+ *
+ * @return the {@link SimpleTypeHolder} for this dialect.
+ * @see #getSimpleTypes()
+ */
+ default SimpleTypeHolder getSimpleTypeHolder() {
+ return new SimpleTypeHolder(new HashSet<>(getSimpleTypes()), true);
+ }
+
/**
* Return the {@link LimitClause} used by this dialect.
*
* @return the {@link LimitClause} used by this dialect.
*/
LimitClause limit();
+
+ /**
+ * Returns the array support object that describes how array-typed columns are supported by this dialect.
+ *
+ * @return the array support object that describes how array-typed columns are supported by this dialect.
+ */
+ default ArrayColumns getArraySupport() {
+ return Unsupported.INSTANCE;
+ }
}
diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java b/src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java
index fd5da050..d0b1bf0b 100644
--- a/src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java
+++ b/src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java
@@ -1,5 +1,20 @@
package org.springframework.data.r2dbc.dialect;
+import lombok.RequiredArgsConstructor;
+
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+import org.springframework.data.mapping.model.SimpleTypeHolder;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
/**
* An SQL dialect for Postgres.
*
@@ -7,6 +22,9 @@
*/
public class PostgresDialect implements Dialect {
+ private static final Set> SIMPLE_TYPES = new HashSet<>(
+ Arrays.asList(UUID.class, URL.class, URI.class, InetAddress.class));
+
/**
* Singleton instance.
*/
@@ -44,6 +62,8 @@ public Position getClausePosition() {
}
};
+ private final PostgresArrayColumns ARRAY_COLUMNS = new PostgresArrayColumns(getSimpleTypeHolder());
+
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.dialect.Dialect#getBindMarkersFactory()
@@ -62,6 +82,15 @@ public String generatedKeysClause() {
return "RETURNING *";
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.Dialect#getSimpleTypesKeys()
+ */
+ @Override
+ public Collection extends Class>> getSimpleTypes() {
+ return SIMPLE_TYPES;
+ }
+
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.dialect.Dialect#limit()
@@ -70,4 +99,44 @@ public String generatedKeysClause() {
public LimitClause limit() {
return LIMIT_CLAUSE;
}
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.Dialect#getArraySupport()
+ */
+ @Override
+ public ArrayColumns getArraySupport() {
+ return ARRAY_COLUMNS;
+ }
+
+ @RequiredArgsConstructor
+ static class PostgresArrayColumns implements ArrayColumns {
+
+ private final SimpleTypeHolder simpleTypes;
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.ArrayColumns#isSupported()
+ */
+ @Override
+ public boolean isSupported() {
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.ArrayColumns#getArrayType(java.lang.Class)
+ */
+ @Override
+ public Class> getArrayType(Class> userType) {
+
+ Assert.notNull(userType, "Array component type must not be null");
+
+ if (!simpleTypes.isSimpleType(userType)) {
+ throw new IllegalArgumentException("Unsupported array type: " + ClassUtils.getQualifiedName(userType));
+ }
+
+ return ClassUtils.resolvePrimitiveIfNecessary(userType);
+ }
+ }
}
diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/SqlServerDialect.java b/src/main/java/org/springframework/data/r2dbc/dialect/SqlServerDialect.java
index ccbc993a..bf7199e0 100644
--- a/src/main/java/org/springframework/data/r2dbc/dialect/SqlServerDialect.java
+++ b/src/main/java/org/springframework/data/r2dbc/dialect/SqlServerDialect.java
@@ -1,5 +1,11 @@
package org.springframework.data.r2dbc.dialect;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
/**
* An SQL dialect for Microsoft SQL Server.
*
@@ -7,6 +13,8 @@
*/
public class SqlServerDialect implements Dialect {
+ private static final Set> SIMPLE_TYPES = new HashSet<>(Collections.singletonList(UUID.class));
+
/**
* Singleton instance.
*/
@@ -63,6 +71,15 @@ public String generatedKeysClause() {
return "select SCOPE_IDENTITY() AS GENERATED_KEYS";
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.Dialect#getSimpleTypesKeys()
+ */
+ @Override
+ public Collection extends Class>> getSimpleTypes() {
+ return SIMPLE_TYPES;
+ }
+
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.dialect.Dialect#limit()
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 dcf77534..537dcdd0 100644
--- a/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java
+++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java
@@ -19,6 +19,7 @@
import io.r2dbc.spi.RowMetadata;
import io.r2dbc.spi.Statement;
+import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -29,22 +30,29 @@
import java.util.function.BiFunction;
import java.util.function.Function;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.dao.InvalidDataAccessResourceUsageException;
+import org.springframework.data.convert.CustomConversions.StoreConversions;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mapping.PersistentPropertyAccessor;
+import org.springframework.data.mapping.context.MappingContext;
+import org.springframework.data.r2dbc.dialect.ArrayColumns;
import org.springframework.data.r2dbc.dialect.BindMarker;
import org.springframework.data.r2dbc.dialect.BindMarkers;
import org.springframework.data.r2dbc.dialect.Dialect;
import org.springframework.data.r2dbc.dialect.LimitClause;
import org.springframework.data.r2dbc.dialect.LimitClause.Position;
import org.springframework.data.r2dbc.function.convert.EntityRowMapper;
+import org.springframework.data.r2dbc.function.convert.R2dbcCustomConversions;
import org.springframework.data.r2dbc.function.convert.SettableValue;
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.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -57,8 +65,9 @@
*/
public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStrategy {
- private final RelationalConverter relationalConverter;
private final Dialect dialect;
+ private final RelationalConverter relationalConverter;
+ private final MappingContext, ? extends RelationalPersistentProperty> mappingContext;
/**
* Creates a new {@link DefaultReactiveDataAccessStrategy} given {@link Dialect}.
@@ -66,7 +75,28 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra
* @param dialect the {@link Dialect} to use.
*/
public DefaultReactiveDataAccessStrategy(Dialect dialect) {
- this(dialect, new BasicRelationalConverter(new RelationalMappingContext()));
+ this(dialect, createConverter(dialect));
+ }
+
+ private static BasicRelationalConverter createConverter(Dialect dialect) {
+
+ Assert.notNull(dialect, "Dialect must not be null");
+
+ R2dbcCustomConversions customConversions = new R2dbcCustomConversions(
+ StoreConversions.of(dialect.getSimpleTypeHolder()), Collections.emptyList());
+
+ RelationalMappingContext context = new RelationalMappingContext();
+ context.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
+
+ return new BasicRelationalConverter(context, customConversions);
+ }
+
+ public RelationalConverter getRelationalConverter() {
+ return relationalConverter;
+ }
+
+ public MappingContext, ? extends RelationalPersistentProperty> getMappingContext() {
+ return mappingContext;
}
/**
@@ -75,12 +105,15 @@ public DefaultReactiveDataAccessStrategy(Dialect dialect) {
* @param dialect the {@link Dialect} to use.
* @param converter must not be {@literal null}.
*/
+ @SuppressWarnings("unchecked")
public DefaultReactiveDataAccessStrategy(Dialect dialect, RelationalConverter converter) {
Assert.notNull(dialect, "Dialect must not be null");
Assert.notNull(converter, "RelationalConverter must not be null");
this.relationalConverter = converter;
+ this.mappingContext = (MappingContext, ? extends RelationalPersistentProperty>) relationalConverter
+ .getMappingContext();
this.dialect = dialect;
}
@@ -121,7 +154,7 @@ public List getValuesToInsert(Object object) {
for (RelationalPersistentProperty property : entity) {
- Object value = propertyAccessor.getProperty(property);
+ Object value = getWriteValue(propertyAccessor, property);
if (value == null) {
continue;
@@ -133,6 +166,31 @@ public List getValuesToInsert(Object object) {
return values;
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getColumnsToUpdate(java.lang.Object)
+ */
+ public Map getColumnsToUpdate(Object object) {
+
+ Assert.notNull(object, "Entity object must not be null!");
+
+ Class> userClass = ClassUtils.getUserClass(object);
+ RelationalPersistentEntity> entity = getRequiredPersistentEntity(userClass);
+
+ Map update = new LinkedHashMap<>();
+
+ PersistentPropertyAccessor propertyAccessor = entity.getPropertyAccessor(object);
+
+ for (RelationalPersistentProperty property : entity) {
+
+ Object writeValue = getWriteValue(propertyAccessor, property);
+
+ update.put(property.getColumnName(), new SettableValue(property.getColumnName(), writeValue, property.getType()));
+ }
+
+ return update;
+ }
+
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getMappedSort(java.lang.Class, org.springframework.data.domain.Sort)
@@ -181,12 +239,53 @@ public String getTableName(Class> type) {
}
private RelationalPersistentEntity> getRequiredPersistentEntity(Class> typeToRead) {
- return relationalConverter.getMappingContext().getRequiredPersistentEntity(typeToRead);
+ return mappingContext.getRequiredPersistentEntity(typeToRead);
}
@Nullable
private RelationalPersistentEntity> getPersistentEntity(Class> typeToRead) {
- return relationalConverter.getMappingContext().getPersistentEntity(typeToRead);
+ return mappingContext.getPersistentEntity(typeToRead);
+ }
+
+ private Object getWriteValue(PersistentPropertyAccessor propertyAccessor, RelationalPersistentProperty property) {
+
+ TypeInformation> type = property.getTypeInformation();
+ Object value = propertyAccessor.getProperty(property);
+
+ if (type.isCollectionLike()) {
+
+ RelationalPersistentEntity> nestedEntity = mappingContext
+ .getPersistentEntity(type.getRequiredActualType().getType());
+
+ if (nestedEntity != null) {
+ throw new InvalidDataAccessApiUsageException("Nested entities are not supported");
+ }
+
+ ArrayColumns arrayColumns = dialect.getArraySupport();
+
+ if (!arrayColumns.isSupported()) {
+
+ throw new InvalidDataAccessResourceUsageException(
+ "Dialect " + dialect.getClass().getName() + " does not support array columns");
+ }
+
+ return getArrayValue(arrayColumns, property, value);
+ }
+
+ return value;
+ }
+
+ private Object getArrayValue(ArrayColumns arrayColumns, RelationalPersistentProperty property, Object value) {
+
+ Class> targetType = arrayColumns.getArrayType(property.getActualType());
+
+ if (!property.isArray() || !property.getActualType().equals(targetType)) {
+
+ Object zeroLengthArray = Array.newInstance(targetType, 0);
+ return relationalConverter.getConversionService().convert(value, zeroLengthArray.getClass());
+ }
+
+ return value;
}
/*
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 f3c8a9f2..8b038e98 100644
--- a/src/main/java/org/springframework/data/r2dbc/function/ReactiveDataAccessStrategy.java
+++ b/src/main/java/org/springframework/data/r2dbc/function/ReactiveDataAccessStrategy.java
@@ -20,6 +20,7 @@
import io.r2dbc.spi.Statement;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
@@ -49,6 +50,14 @@ public interface ReactiveDataAccessStrategy {
*/
List getValuesToInsert(Object object);
+ /**
+ * Returns a {@link Map} that maps column names to a {@link SettableValue} value.
+ *
+ * @param object must not be {@literal null}.
+ * @return
+ */
+ Map getColumnsToUpdate(Object object);
+
/**
* Map the {@link Sort} object to apply field name mapping using {@link Class the type to read}.
*
diff --git a/src/main/java/org/springframework/data/r2dbc/function/convert/EntityRowMapper.java b/src/main/java/org/springframework/data/r2dbc/function/convert/EntityRowMapper.java
index c1c67b26..515718cc 100644
--- a/src/main/java/org/springframework/data/r2dbc/function/convert/EntityRowMapper.java
+++ b/src/main/java/org/springframework/data/r2dbc/function/convert/EntityRowMapper.java
@@ -23,7 +23,6 @@
import java.sql.ResultSet;
import java.util.function.BiFunction;
-import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
@@ -39,7 +38,7 @@
* Maps a {@link io.r2dbc.spi.Row} to an entity of type {@code T}, including entities referenced.
*
* @author Mark Paluch
- * @since 1.0
+ * @author Ryland Degnan
*/
public class EntityRowMapper implements BiFunction {
@@ -52,13 +51,17 @@ public EntityRowMapper(RelationalPersistentEntity entity, RelationalConverter
this.converter = converter;
}
+ /*
+ * (non-Javadoc)
+ * @see java.util.function.BiFunction#apply(java.lang.Object, java.lang.Object)
+ */
@Override
public T apply(Row row, RowMetadata metadata) {
T result = createInstance(row, "", entity);
- ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(result),
- converter.getConversionService());
+ ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor<>(
+ entity.getPropertyAccessor(result), converter.getConversionService());
for (RelationalPersistentProperty property : entity) {
@@ -66,10 +69,7 @@ public T apply(Row row, RowMetadata metadata) {
continue;
}
- if (property.isCollectionLike()) {
- throw new UnsupportedOperationException();
- } else if (property.isMap()) {
-
+ if (property.isMap()) {
throw new UnsupportedOperationException();
} else {
propertyAccessor.setProperty(property, readFrom(row, property, ""));
@@ -96,7 +96,8 @@ private Object readFrom(Row row, RelationalPersistentProperty property, String p
return readEntityFrom(row, property);
}
- return row.get(prefix + property.getColumnName());
+ Object value = row.get(prefix + property.getColumnName());
+ return converter.readValue(value, property.getTypeInformation());
} catch (Exception o_O) {
throw new MappingException(String.format("Could not read property %s from result set!", property), o_O);
@@ -107,7 +108,6 @@ private S readEntityFrom(Row row, PersistentProperty> property) {
String prefix = property.getName() + "_";
- @SuppressWarnings("unchecked")
RelationalPersistentEntity entity = (RelationalPersistentEntity) converter.getMappingContext()
.getRequiredPersistentEntity(property.getActualType());
@@ -117,8 +117,8 @@ private S readEntityFrom(Row row, PersistentProperty> property) {
S instance = createInstance(row, prefix, entity);
- PersistentPropertyAccessor accessor = entity.getPropertyAccessor(instance);
- ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(accessor,
+ PersistentPropertyAccessor accessor = entity.getPropertyAccessor(instance);
+ ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor<>(accessor,
converter.getConversionService());
for (RelationalPersistentProperty p : entity) {
@@ -132,8 +132,7 @@ private S readEntityFrom(Row row, PersistentProperty> property) {
private S createInstance(Row row, String prefix, RelationalPersistentEntity entity) {
- RowParameterValueProvider rowParameterValueProvider = new RowParameterValueProvider(row, entity,
- converter.getConversionService(), prefix);
+ RowParameterValueProvider rowParameterValueProvider = new RowParameterValueProvider(row, entity, converter, prefix);
return converter.createInstance(entity, rowParameterValueProvider::getParameterValue);
}
@@ -143,7 +142,7 @@ private static class RowParameterValueProvider implements ParameterValueProvider
private final @NonNull Row resultSet;
private final @NonNull RelationalPersistentEntity> entity;
- private final @NonNull ConversionService conversionService;
+ private final @NonNull RelationalConverter converter;
private final @NonNull String prefix;
/*
@@ -154,10 +153,11 @@ private static class RowParameterValueProvider implements ParameterValueProvider
@Nullable
public T getParameterValue(Parameter parameter) {
- String column = prefix + entity.getRequiredPersistentProperty(parameter.getName()).getColumnName();
+ RelationalPersistentProperty property = entity.getRequiredPersistentProperty(parameter.getName());
+ String column = prefix + property.getColumnName();
try {
- return conversionService.convert(resultSet.get(column), parameter.getType().getType());
+ return converter.getConversionService().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 6b291e86..54b26ec1 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
@@ -21,7 +21,6 @@
import java.util.LinkedHashMap;
import java.util.Map;
-import java.util.Optional;
import java.util.function.BiFunction;
import org.springframework.core.convert.ConversionService;
@@ -65,32 +64,6 @@ public MappingR2dbcConverter(RelationalConverter converter) {
this.relationalConverter = converter;
}
- /**
- * Returns a {@link Map} that maps column names to an {@link Optional} value. Used {@link Optional#empty()} if the
- * underlying property is {@literal null}.
- *
- * @param object must not be {@literal null}.
- * @return
- */
- public Map getColumnsToUpdate(Object object) {
-
- Assert.notNull(object, "Entity object must not be null!");
-
- Class> userClass = ClassUtils.getUserClass(object);
- RelationalPersistentEntity> entity = getMappingContext().getRequiredPersistentEntity(userClass);
-
- Map update = new LinkedHashMap<>();
-
- PersistentPropertyAccessor propertyAccessor = entity.getPropertyAccessor(object);
-
- for (RelationalPersistentProperty property : entity) {
- update.put(property.getColumnName(),
- new SettableValue(property.getColumnName(), propertyAccessor.getProperty(property), property.getType()));
- }
-
- return update;
- }
-
/**
* Returns a {@link java.util.function.Function} that populates the id property of the {@code object} from a
* {@link Row}.
diff --git a/src/main/java/org/springframework/data/r2dbc/function/convert/R2dbcCustomConversions.java b/src/main/java/org/springframework/data/r2dbc/function/convert/R2dbcCustomConversions.java
new file mode 100644
index 00000000..8c3a692e
--- /dev/null
+++ b/src/main/java/org/springframework/data/r2dbc/function/convert/R2dbcCustomConversions.java
@@ -0,0 +1,26 @@
+package org.springframework.data.r2dbc.function.convert;
+
+import java.util.Collection;
+
+import org.springframework.data.convert.CustomConversions;
+
+/**
+ * Value object to capture custom conversion. {@link R2dbcCustomConversions} also act as factory for
+ * {@link org.springframework.data.mapping.model.SimpleTypeHolder}
+ *
+ * @author Mark Paluch
+ * @see CustomConversions
+ * @see org.springframework.data.mapping.model.SimpleTypeHolder
+ */
+public class R2dbcCustomConversions extends CustomConversions {
+
+ /**
+ * Creates a new {@link CustomConversions} instance registering the given converters.
+ *
+ * @param storeConversions must not be {@literal null}.
+ * @param converters must not be {@literal null}.
+ */
+ public R2dbcCustomConversions(StoreConversions storeConversions, Collection> converters) {
+ super(storeConversions, converters);
+ }
+}
diff --git a/src/main/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoriesRegistrar.java b/src/main/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoriesRegistrar.java
index 5279e09f..91949b31 100644
--- a/src/main/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoriesRegistrar.java
+++ b/src/main/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoriesRegistrar.java
@@ -24,7 +24,6 @@
* R2DBC-specific {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar}.
*
* @author Mark Paluch
- * @since 2.0
*/
class R2dbcRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport {
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 23830347..ce2e4a55 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
@@ -71,7 +71,7 @@ public Mono save(S objectToSave) {
}
Object id = entity.getRequiredId(objectToSave);
- Map columns = converter.getColumnsToUpdate(objectToSave);
+ Map columns = accessStrategy.getColumnsToUpdate(objectToSave);
columns.remove(getIdColumnName()); // do not update the Id column.
String idColumnName = getIdColumnName();
BindIdOperation update = accessStrategy.updateById(entity.getTableName(), columns.keySet(), idColumnName);
diff --git a/src/test/java/org/springframework/data/r2dbc/dialect/PostgresDialectUnitTests.java b/src/test/java/org/springframework/data/r2dbc/dialect/PostgresDialectUnitTests.java
index 3b0168d5..e1fe8607 100644
--- a/src/test/java/org/springframework/data/r2dbc/dialect/PostgresDialectUnitTests.java
+++ b/src/test/java/org/springframework/data/r2dbc/dialect/PostgresDialectUnitTests.java
@@ -1,8 +1,12 @@
package org.springframework.data.r2dbc.dialect;
import static org.assertj.core.api.Assertions.*;
+import static org.assertj.core.api.SoftAssertions.*;
+
+import java.util.List;
import org.junit.Test;
+import org.springframework.data.mapping.model.SimpleTypeHolder;
/**
* Unit tests for {@link PostgresDialect}.
@@ -22,4 +26,52 @@ public void shouldUsePostgresPlaceholders() {
assertThat(first.getPlaceholder()).isEqualTo("$1");
assertThat(second.getPlaceholder()).isEqualTo("$2");
}
+
+ @Test // gh-30
+ public void shouldConsiderSimpleTypes() {
+
+ SimpleTypeHolder holder = PostgresDialect.INSTANCE.getSimpleTypeHolder();
+
+ assertSoftly(it -> {
+ it.assertThat(holder.isSimpleType(String.class)).isTrue();
+ it.assertThat(holder.isSimpleType(int.class)).isTrue();
+ it.assertThat(holder.isSimpleType(Integer.class)).isTrue();
+ });
+ }
+
+ @Test // gh-30
+ public void shouldSupportArrays() {
+
+ ArrayColumns arrayColumns = PostgresDialect.INSTANCE.getArraySupport();
+
+ assertThat(arrayColumns.isSupported()).isTrue();
+ }
+
+ @Test // gh-30
+ public void shouldUseBoxedArrayTypesForPrimitiveTypes() {
+
+ ArrayColumns arrayColumns = PostgresDialect.INSTANCE.getArraySupport();
+
+ assertSoftly(it -> {
+ it.assertThat(arrayColumns.getArrayType(int.class)).isEqualTo(Integer.class);
+ it.assertThat(arrayColumns.getArrayType(double.class)).isEqualTo(Double.class);
+ it.assertThat(arrayColumns.getArrayType(String.class)).isEqualTo(String.class);
+ });
+ }
+
+ @Test // gh-30
+ public void shouldRejectNonSimpleArrayTypes() {
+
+ ArrayColumns arrayColumns = PostgresDialect.INSTANCE.getArraySupport();
+
+ assertThatThrownBy(() -> arrayColumns.getArrayType(getClass())).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test // gh-30
+ public void shouldRejectNestedCollections() {
+
+ ArrayColumns arrayColumns = PostgresDialect.INSTANCE.getArraySupport();
+
+ assertThatThrownBy(() -> arrayColumns.getArrayType(List.class)).isInstanceOf(IllegalArgumentException.class);
+ }
}
diff --git a/src/test/java/org/springframework/data/r2dbc/dialect/SqlServerDialectUnitTests.java b/src/test/java/org/springframework/data/r2dbc/dialect/SqlServerDialectUnitTests.java
index 0e848015..cb39be65 100644
--- a/src/test/java/org/springframework/data/r2dbc/dialect/SqlServerDialectUnitTests.java
+++ b/src/test/java/org/springframework/data/r2dbc/dialect/SqlServerDialectUnitTests.java
@@ -2,7 +2,10 @@
import static org.assertj.core.api.Assertions.*;
+import java.util.UUID;
+
import org.junit.Test;
+import org.springframework.data.mapping.model.SimpleTypeHolder;
/**
* Unit tests for {@link SqlServerDialect}.
@@ -22,4 +25,21 @@ public void shouldUseNamedPlaceholders() {
assertThat(first.getPlaceholder()).isEqualTo("@P0");
assertThat(second.getPlaceholder()).isEqualTo("@P1_foobar");
}
+
+ @Test // gh-30
+ public void shouldConsiderUuidAsSimple() {
+
+ SimpleTypeHolder holder = SqlServerDialect.INSTANCE.getSimpleTypeHolder();
+
+ assertThat(holder.isSimpleType(UUID.class)).isTrue();
+ }
+
+ @Test // gh-30
+ public void shouldNotSupportArrays() {
+
+ ArrayColumns arrayColumns = SqlServerDialect.INSTANCE.getArraySupport();
+
+ assertThat(arrayColumns.isSupported()).isFalse();
+ assertThatThrownBy(() -> arrayColumns.getArrayType(String.class)).isInstanceOf(UnsupportedOperationException.class);
+ }
}
diff --git a/src/test/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategyUnitTests.java b/src/test/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategyUnitTests.java
index df279946..120d6d4e 100644
--- a/src/test/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategyUnitTests.java
+++ b/src/test/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategyUnitTests.java
@@ -8,9 +8,12 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
import org.junit.Test;
import org.springframework.data.r2dbc.dialect.PostgresDialect;
+import org.springframework.data.r2dbc.function.convert.SettableValue;
/**
* Unit tests for {@link DefaultReactiveDataAccessStrategy}.
@@ -101,4 +104,41 @@ public void shouldRenderDeleteByIdInQuery() {
operation.bindId(statement, "bar");
assertThat(operation.toQuery()).isEqualTo("DELETE FROM table WHERE id IN ($1, $2)");
}
+
+ @Test // gh-22
+ public void shouldUpdateArray() {
+
+ Map columnsToUpdate = strategy
+ .getColumnsToUpdate(new WithCollectionTypes(new String[] { "one", "two" }, null));
+
+ Object stringArray = columnsToUpdate.get("string_array").getValue();
+
+ assertThat(stringArray).isInstanceOf(String[].class);
+ assertThat((String[]) stringArray).hasSize(2).contains("one", "two");
+ }
+
+ @Test // gh-22
+ public void shouldConvertListToArray() {
+
+ Map columnsToUpdate = strategy
+ .getColumnsToUpdate(new WithCollectionTypes(null, Arrays.asList("one", "two")));
+
+ Object stringArray = columnsToUpdate.get("string_collection").getValue();
+
+ assertThat(stringArray).isInstanceOf(String[].class);
+ assertThat((String[]) stringArray).hasSize(2).contains("one", "two");
+ }
+
+ static class WithCollectionTypes {
+
+ String[] stringArray;
+
+ List stringCollection;
+
+ WithCollectionTypes(String[] stringArray, List stringCollection) {
+
+ this.stringArray = stringArray;
+ this.stringCollection = stringCollection;
+ }
+ }
}
diff --git a/src/test/java/org/springframework/data/r2dbc/function/PostgresIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/function/PostgresIntegrationTests.java
new file mode 100644
index 00000000..8321eddb
--- /dev/null
+++ b/src/test/java/org/springframework/data/r2dbc/function/PostgresIntegrationTests.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.r2dbc.function;
+
+import static org.assertj.core.api.Assertions.*;
+
+import io.r2dbc.spi.ConnectionFactory;
+import lombok.AllArgsConstructor;
+import reactor.test.StepVerifier;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+
+import javax.sql.DataSource;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.springframework.data.r2dbc.testing.ExternalDatabase;
+import org.springframework.data.r2dbc.testing.PostgresTestSupport;
+import org.springframework.data.r2dbc.testing.R2dbcIntegrationTestSupport;
+import org.springframework.data.relational.core.mapping.Table;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+/**
+ * Integration tests for PostgreSQL-specific features such as array support.
+ *
+ * @author Mark Paluch
+ */
+public class PostgresIntegrationTests extends R2dbcIntegrationTestSupport {
+
+ @ClassRule public static final ExternalDatabase database = PostgresTestSupport.database();
+
+ DataSource dataSource = PostgresTestSupport.createDataSource(database);
+ ConnectionFactory connectionFactory = PostgresTestSupport.createConnectionFactory(database);
+ JdbcTemplate template = createJdbcTemplate(dataSource);
+ DatabaseClient client = DatabaseClient.create(connectionFactory);
+
+ @Before
+ public void before() {
+
+ template.execute("DROP TABLE IF EXISTS with_arrays");
+ template.execute("CREATE TABLE with_arrays (" //
+ + "boxed_array INT[]," //
+ + "primitive_array INT[]," //
+ + "multidimensional_array INT[]," //
+ + "collection_array INT[][])");
+ }
+
+ @Test // gh-30
+ @Ignore("https://github.com/r2dbc/r2dbc-postgresql/issues/40, r2dbc-postgresql returns Object[] instead of Integer[]")
+ public void shouldReadAndWritePrimitiveSingleDimensionArrays() {
+
+ EntityWithArrays withArrays = new EntityWithArrays(null, new int[] { 1, 2, 3 }, null, null);
+
+ insert(withArrays);
+ selectAndAssert(actual -> {
+ assertThat(actual.primitiveArray).containsExactly(1, 2, 3);
+ });
+ }
+
+ @Test // gh-30
+ public void shouldReadAndWriteBoxedSingleDimensionArrays() {
+
+ EntityWithArrays withArrays = new EntityWithArrays(new Integer[] { 1, 2, 3 }, null, null, null);
+
+ insert(withArrays);
+
+ selectAndAssert(actual -> {
+
+ assertThat(actual.boxedArray).containsExactly(1, 2, 3);
+
+ });
+ }
+
+ @Test // gh-30
+ public void shouldReadAndWriteConvertedDimensionArrays() {
+
+ EntityWithArrays withArrays = new EntityWithArrays(null, null, null, Arrays.asList(5, 6, 7));
+
+ insert(withArrays);
+
+ selectAndAssert(actual -> {
+ assertThat(actual.collectionArray).containsExactly(5, 6, 7);
+ });
+ }
+
+ @Test // gh-30
+ @Ignore("https://github.com/r2dbc/r2dbc-postgresql/issues/42, Multi-dimensional arrays not supported yet")
+ public void shouldReadAndWriteMultiDimensionArrays() {
+
+ EntityWithArrays withArrays = new EntityWithArrays(null, null, new int[][] { { 1, 2, 3 }, { 4, 5 } }, null);
+
+ insert(withArrays);
+
+ selectAndAssert(actual -> {
+
+ assertThat(actual.multidimensionalArray).hasSize(2);
+ assertThat(actual.multidimensionalArray[0]).containsExactly(1, 2, 3);
+ assertThat(actual.multidimensionalArray[1]).containsExactly(4, 5, 6);
+ });
+ }
+
+ private void insert(EntityWithArrays object) {
+
+ client.insert() //
+ .into(EntityWithArrays.class) //
+ .using(object) //
+ .then() //
+ .as(StepVerifier::create) //
+ .verifyComplete();
+ }
+
+ private void selectAndAssert(Consumer super EntityWithArrays> assertion) {
+
+ client.select() //
+ .from(EntityWithArrays.class).fetch() //
+ .first() //
+ .as(StepVerifier::create) //
+ .consumeNextWith(assertion).verifyComplete();
+ }
+
+ @Table("with_arrays")
+ @AllArgsConstructor
+ static class EntityWithArrays {
+
+ Integer[] boxedArray;
+ int[] primitiveArray;
+ int[][] multidimensionalArray;
+ List collectionArray;
+ }
+}
diff --git a/src/test/java/org/springframework/data/r2dbc/function/convert/EntityRowMapperUnitTests.java b/src/test/java/org/springframework/data/r2dbc/function/convert/EntityRowMapperUnitTests.java
new file mode 100644
index 00000000..91dceff2
--- /dev/null
+++ b/src/test/java/org/springframework/data/r2dbc/function/convert/EntityRowMapperUnitTests.java
@@ -0,0 +1,131 @@
+package org.springframework.data.r2dbc.function.convert;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import io.r2dbc.spi.Row;
+import io.r2dbc.spi.RowMetadata;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.data.r2dbc.dialect.PostgresDialect;
+import org.springframework.data.r2dbc.function.DefaultReactiveDataAccessStrategy;
+import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
+
+/**
+ * Unit tests for {@link EntityRowMapper}.
+ *
+ * @author Mark Paluch
+ * @author Jens Schauder
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class EntityRowMapperUnitTests {
+
+ DefaultReactiveDataAccessStrategy strategy = new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE);
+
+ Row rowMock = mock(Row.class);
+ RowMetadata metadata = mock(RowMetadata.class);
+
+ @Test // gh-22
+ public void shouldMapSimpleEntity() {
+
+ EntityRowMapper mapper = getRowMapper(SimpleEntity.class);
+ when(rowMock.get("id")).thenReturn("foo");
+
+ SimpleEntity result = mapper.apply(rowMock, metadata);
+ assertThat(result.id).isEqualTo("foo");
+ }
+
+ @Test // gh-22
+ public void shouldMapSimpleEntityWithConstructorCreation() {
+
+ EntityRowMapper mapper = getRowMapper(SimpleEntityConstructorCreation.class);
+ when(rowMock.get("id")).thenReturn("foo");
+
+ SimpleEntityConstructorCreation result = mapper.apply(rowMock, metadata);
+ assertThat(result.id).isEqualTo("foo");
+ }
+
+ @Test // gh-22
+ public void shouldApplyConversionWithConstructorCreation() {
+
+ EntityRowMapper mapper = getRowMapper(ConversionWithConstructorCreation.class);
+ when(rowMock.get("id")).thenReturn((byte) 0x24);
+
+ ConversionWithConstructorCreation result = mapper.apply(rowMock, metadata);
+ assertThat(result.id).isEqualTo(36L);
+ }
+
+ @Test // gh-30
+ public void shouldConvertArrayToCollection() {
+
+ EntityRowMapper mapper = getRowMapper(EntityWithCollection.class);
+ when(rowMock.get("ids")).thenReturn((new String[] { "foo", "bar" }));
+
+ EntityWithCollection result = mapper.apply(rowMock, metadata);
+ assertThat(result.ids).contains("foo", "bar");
+ }
+
+ @Test // gh-30
+ public void shouldConvertArrayToSet() {
+
+ EntityRowMapper mapper = getRowMapper(EntityWithCollection.class);
+ when(rowMock.get("integer_set")).thenReturn((new int[] { 3, 14 }));
+
+ EntityWithCollection result = mapper.apply(rowMock, metadata);
+ assertThat(result.integerSet).contains(3, 14);
+ }
+
+ @Test // gh-30
+ public void shouldConvertArrayMembers() {
+
+ EntityRowMapper mapper = getRowMapper(EntityWithCollection.class);
+ when(rowMock.get("primitive_integers")).thenReturn((new Long[] { 3L, 14L }));
+
+ EntityWithCollection result = mapper.apply(rowMock, metadata);
+ assertThat(result.primitiveIntegers).contains(3, 14);
+ }
+
+ @Test // gh-30
+ public void shouldConvertArrayToBoxedArray() {
+
+ EntityRowMapper mapper = getRowMapper(EntityWithCollection.class);
+ when(rowMock.get("boxed_integers")).thenReturn((new int[] { 3, 11 }));
+
+ EntityWithCollection result = mapper.apply(rowMock, metadata);
+ assertThat(result.boxedIntegers).contains(3, 11);
+ }
+
+ @SuppressWarnings("unchecked")
+ private EntityRowMapper getRowMapper(Class type) {
+ RelationalPersistentEntity entity = (RelationalPersistentEntity) strategy.getMappingContext()
+ .getRequiredPersistentEntity(type);
+ return new EntityRowMapper<>(entity, strategy.getRelationalConverter());
+ }
+
+ static class SimpleEntity {
+ String id;
+ }
+
+ @RequiredArgsConstructor
+ static class SimpleEntityConstructorCreation {
+ final String id;
+ }
+
+ @RequiredArgsConstructor
+ static class ConversionWithConstructorCreation {
+ final long id;
+ }
+
+ static class EntityWithCollection {
+ List ids;
+ Set integerSet;
+ Integer[] boxedIntegers;
+ int[] primitiveIntegers;
+ }
+}