diff --git a/pom.xml b/pom.xml
index eaee823fa0..deb60a8dbb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
org.springframework.data
spring-data-relational-parent
- 4.0.0-SNAPSHOT
+ 4.0.0-2078-custom-converter-in-collection-SNAPSHOT
pom
Spring Data Relational Parent
diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml
index b3c39e64c3..b422d3dde8 100644
--- a/spring-data-jdbc-distribution/pom.xml
+++ b/spring-data-jdbc-distribution/pom.xml
@@ -14,7 +14,7 @@
org.springframework.data
spring-data-relational-parent
- 4.0.0-SNAPSHOT
+ 4.0.0-2078-custom-converter-in-collection-SNAPSHOT
../pom.xml
diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml
index e61fd64020..e8a3ff4ed5 100644
--- a/spring-data-jdbc/pom.xml
+++ b/spring-data-jdbc/pom.xml
@@ -6,7 +6,7 @@
4.0.0
spring-data-jdbc
- 4.0.0-SNAPSHOT
+ 4.0.0-2078-custom-converter-in-collection-SNAPSHOT
Spring Data JDBC
Spring Data module for JDBC repositories.
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-relational-parent
- 4.0.0-SNAPSHOT
+ 4.0.0-2078-custom-converter-in-collection-SNAPSHOT
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java
index 2c3feffdb6..6de2102eed 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java
@@ -255,9 +255,11 @@ public JdbcValue writeJdbcValue(@Nullable Object value, TypeInformation> colum
}
Class> componentType = convertedValue.getClass().getComponentType();
+
if (componentType != byte.class && componentType != Byte.class) {
Object[] objectArray = requireObjectArray(convertedValue);
+
return JdbcValue.of(typeFactory.createArray(objectArray), JDBCType.ARRAY);
}
@@ -268,6 +270,21 @@ public JdbcValue writeJdbcValue(@Nullable Object value, TypeInformation> colum
return JdbcValue.of(convertedValue, JDBCType.BINARY);
}
+ /**
+ * Unwraps values of type {@link JdbcValue}.
+ *
+ * @param convertedValue a value that might need unwrapping.
+ */
+ @Override
+ @Nullable
+ protected Object unwrap(@Nullable Object convertedValue) {
+
+ if (convertedValue instanceof JdbcValue jdbcValue) {
+ return jdbcValue.getValue();
+ }
+ return convertedValue;
+ }
+
@SuppressWarnings("unchecked")
@Override
public R readAndResolve(TypeInformation type, RowDocument source, Identifier identifier) {
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java
index d8f823f12d..39eff02265 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java
@@ -38,8 +38,10 @@
import org.springframework.data.jdbc.core.mapping.JdbcValue;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
+import org.springframework.data.jdbc.testing.EnabledOnFeature;
import org.springframework.data.jdbc.testing.IntegrationTest;
import org.springframework.data.jdbc.testing.TestConfiguration;
+import org.springframework.data.jdbc.testing.TestDatabaseFeatures;
import org.springframework.data.repository.CrudRepository;
/**
@@ -61,6 +63,11 @@ EntityWithStringyBigDecimalRepository repository(JdbcRepositoryFactory factory)
return factory.getRepository(EntityWithStringyBigDecimalRepository.class);
}
+ @Bean
+ EntityWithDirectionsRepository repositoryWithDirections(JdbcRepositoryFactory factory) {
+ return factory.getRepository(EntityWithDirectionsRepository.class);
+ }
+
@Bean
JdbcCustomConversions jdbcCustomConversions() {
return new JdbcCustomConversions(asList(StringToBigDecimalConverter.INSTANCE, BigDecimalToString.INSTANCE,
@@ -70,6 +77,7 @@ JdbcCustomConversions jdbcCustomConversions() {
}
@Autowired EntityWithStringyBigDecimalRepository repository;
+ @Autowired EntityWithDirectionsRepository repositoryWithDirections;
/**
* In PostrgreSQL this fails if a simple converter like the following is used.
@@ -162,6 +170,18 @@ void queryByEnumTypeEqual() {
.containsExactly(Direction.CENTER);
}
+ @Test // GH-2078
+ @EnabledOnFeature(TestDatabaseFeatures.Feature.SUPPORTS_ARRAYS)
+ void saveAndLoadListOfDirectionsAsArray() {
+
+ EntityWithDirections saved = repositoryWithDirections
+ .save(new EntityWithDirections(null, List.of(Direction.CENTER, Direction.RIGHT)));
+
+ EntityWithDirections reloaded = repositoryWithDirections.findById(saved.id).orElseThrow();
+
+ assertThat(reloaded).isEqualTo(saved);
+ }
+
interface EntityWithStringyBigDecimalRepository extends CrudRepository {
@Query("SELECT * FROM ENTITY_WITH_STRINGY_BIG_DECIMAL WHERE DIRECTION IN (:types)")
@@ -171,6 +191,8 @@ interface EntityWithStringyBigDecimalRepository extends CrudRepository findByEnumType(Direction type);
}
+ interface EntityWithDirectionsRepository extends CrudRepository {}
+
private static class EntityWithStringyBigDecimal {
@Id CustomId id;
@@ -194,6 +216,9 @@ private static class OtherEntity {
Date created;
}
+ record EntityWithDirections(@Id Long id, List directions) {
+ }
+
enum Direction {
LEFT, CENTER, RIGHT
}
diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-h2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-h2.sql
index 426153b9e3..b6aebdd4df 100644
--- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-h2.sql
+++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-h2.sql
@@ -1,2 +1,3 @@
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10), DIRECTION INTEGER);
CREATE TABLE OTHER_ENTITY ( ID IDENTITY PRIMARY KEY, CREATED DATE, ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER);
+CREATE TABLE ENTITY_WITH_DIRECTIONS ( ID IDENTITY PRIMARY KEY, DIRECTIONS INTEGER ARRAY);
diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql
index 9508fbb0e2..a56b15ecf2 100644
--- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql
+++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql
@@ -1,3 +1,4 @@
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10), DIRECTION INTEGER);
CREATE TABLE OTHER_ENTITY ( ID IDENTITY PRIMARY KEY, CREATED DATE, ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER);
+CREATE TABLE ENTITY_WITH_DIRECTIONS ( ID IDENTITY PRIMARY KEY, DIRECTIONS INTEGER ARRAY);
diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql
index 882d8df894..1692e6bcdc 100644
--- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql
+++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql
@@ -1,2 +1,3 @@
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id SERIAL PRIMARY KEY, Stringy_number DECIMAL(20,10), DIRECTION INTEGER);
CREATE TABLE OTHER_ENTITY ( ID SERIAL PRIMARY KEY, CREATED DATE, ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER);
+CREATE TABLE ENTITY_WITH_DIRECTIONS ( ID SERIAL PRIMARY KEY, DIRECTIONS INTEGER ARRAY);
diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml
index 3ee76fd3c1..9210bb285c 100644
--- a/spring-data-r2dbc/pom.xml
+++ b/spring-data-r2dbc/pom.xml
@@ -6,7 +6,7 @@
4.0.0
spring-data-r2dbc
- 4.0.0-SNAPSHOT
+ 4.0.0-2078-custom-converter-in-collection-SNAPSHOT
Spring Data R2DBC
Spring Data module for R2DBC
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-relational-parent
- 4.0.0-SNAPSHOT
+ 4.0.0-2078-custom-converter-in-collection-SNAPSHOT
diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml
index 8fd6d7a6f0..31238eea28 100644
--- a/spring-data-relational/pom.xml
+++ b/spring-data-relational/pom.xml
@@ -6,7 +6,7 @@
4.0.0
spring-data-relational
- 4.0.0-SNAPSHOT
+ 4.0.0-2078-custom-converter-in-collection-SNAPSHOT
Spring Data Relational
Spring Data Relational support
@@ -14,7 +14,7 @@
org.springframework.data
spring-data-relational-parent
- 4.0.0-SNAPSHOT
+ 4.0.0-2078-custom-converter-in-collection-SNAPSHOT
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java
index 82e7e133c3..9c123c0244 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java
@@ -44,16 +44,7 @@
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
import org.springframework.data.mapping.context.MappingContext;
-import org.springframework.data.mapping.model.CachingValueExpressionEvaluatorFactory;
-import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
-import org.springframework.data.mapping.model.EntityInstantiator;
-import org.springframework.data.mapping.model.ParameterValueProvider;
-import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
-import org.springframework.data.mapping.model.PropertyValueProvider;
-import org.springframework.data.mapping.model.SimpleTypeHolder;
-import org.springframework.data.mapping.model.SpELContext;
-import org.springframework.data.mapping.model.ValueExpressionEvaluator;
-import org.springframework.data.mapping.model.ValueExpressionParameterValueProvider;
+import org.springframework.data.mapping.model.*;
import org.springframework.data.projection.EntityProjection;
import org.springframework.data.projection.EntityProjectionIntrospector;
import org.springframework.data.projection.EntityProjectionIntrospector.ProjectionPredicate;
@@ -790,14 +781,35 @@ private Object writeCollection(Iterable> value, TypeInformation> type) {
}
for (Object o : value) {
- mapped.add(writeValue(o, component));
+ mapped.add(unwrap(writeValue(o, component)));
}
if (type.getType().isInstance(mapped) || !type.isCollectionLike()) {
return mapped;
}
- return getConversionService().convert(mapped, type.getType());
+ // if we succeeded converting the members of the collection, we actually ignore the fallback targetType since that
+ // was derived without considering custom conversions.
+ Class> targetType = type.getType();
+ if (!mapped.isEmpty()) {
+
+ Class> targetComponentType = mapped.get(0).getClass();
+ targetType = Array.newInstance(targetComponentType, 0).getClass();
+ }
+ return getConversionService().convert(mapped, targetType);
+ }
+
+ /**
+ * Unwraps technology specific wrappers. Custom conversions may choose to return a wrapper class that contains additional information for the technology driver.
+ * These wrappers can't be used as members of a collection, therefore we may have to unwrap the values.
+ *
+ * This method allows technology specific implemenations to provide such an unwrapping mechanism.
+ *
+ * @param convertedValue a value that might need unwrapping.
+ */
+ @Nullable
+ protected Object unwrap(@Nullable Object convertedValue) {
+ return convertedValue;
}
static Predicate isConstructorArgument(PersistentEntity, ?> entity) {