Skip to content

Commit 3fec94a

Browse files
schaudermp911de
authored andcommitted
Apply custom conversions for collections.
The target type for the conversion of the complete collection gets adapted when a custom conversion was applied to the elements. Also `JdbcValue` elements as a result of a custom conversion get unwrapped. Closes #2078 Original pull request: #2081
1 parent 4b47b1e commit 3fec94a

File tree

6 files changed

+72
-2
lines changed

6 files changed

+72
-2
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ public JdbcValue writeJdbcValue(@Nullable Object value, TypeInformation<?> colum
255255
}
256256

257257
Class<?> componentType = convertedValue.getClass().getComponentType();
258+
259+
if (convertedValue.getClass().isArray()) {
260+
258261
if (componentType != byte.class && componentType != Byte.class) {
259262

260263
Object[] objectArray = requireObjectArray(convertedValue);
@@ -268,6 +271,24 @@ public JdbcValue writeJdbcValue(@Nullable Object value, TypeInformation<?> colum
268271
return JdbcValue.of(convertedValue, JDBCType.BINARY);
269272
}
270273

274+
return JdbcValue.of(convertedValue, sqlType);
275+
}
276+
277+
/**
278+
* Unwraps values of type {@link JdbcValue}.
279+
*
280+
* @param convertedValue a value that might need unwrapping.
281+
*/
282+
@Override
283+
@Nullable
284+
protected Object unwrap(@Nullable Object convertedValue) {
285+
286+
if (convertedValue instanceof JdbcValue jdbcValue) {
287+
return jdbcValue.getValue();
288+
}
289+
return convertedValue;
290+
}
291+
271292
@SuppressWarnings("unchecked")
272293
@Override
273294
public <R> R readAndResolve(TypeInformation<R> type, RowDocument source, Identifier identifier) {

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@
3838
import org.springframework.data.jdbc.core.mapping.JdbcValue;
3939
import org.springframework.data.jdbc.repository.query.Query;
4040
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
41+
import org.springframework.data.jdbc.testing.EnabledOnFeature;
4142
import org.springframework.data.jdbc.testing.IntegrationTest;
4243
import org.springframework.data.jdbc.testing.TestConfiguration;
44+
import org.springframework.data.jdbc.testing.TestDatabaseFeatures;
4345
import org.springframework.data.repository.CrudRepository;
4446

4547
/**
@@ -61,6 +63,11 @@ EntityWithStringyBigDecimalRepository repository(JdbcRepositoryFactory factory)
6163
return factory.getRepository(EntityWithStringyBigDecimalRepository.class);
6264
}
6365

66+
@Bean
67+
EntityWithDirectionsRepository repositoryWithDirections(JdbcRepositoryFactory factory) {
68+
return factory.getRepository(EntityWithDirectionsRepository.class);
69+
}
70+
6471
@Bean
6572
JdbcCustomConversions jdbcCustomConversions() {
6673
return new JdbcCustomConversions(asList(StringToBigDecimalConverter.INSTANCE, BigDecimalToString.INSTANCE,
@@ -70,6 +77,7 @@ JdbcCustomConversions jdbcCustomConversions() {
7077
}
7178

7279
@Autowired EntityWithStringyBigDecimalRepository repository;
80+
@Autowired EntityWithDirectionsRepository repositoryWithDirections;
7381

7482
/**
7583
* In PostrgreSQL this fails if a simple converter like the following is used.
@@ -162,6 +170,18 @@ void queryByEnumTypeEqual() {
162170
.containsExactly(Direction.CENTER);
163171
}
164172

173+
@Test // GH-2078
174+
@EnabledOnFeature(TestDatabaseFeatures.Feature.SUPPORTS_ARRAYS)
175+
void saveAndLoadListOfDirectionsAsArray() {
176+
177+
EntityWithDirections saved = repositoryWithDirections
178+
.save(new EntityWithDirections(null, List.of(Direction.CENTER, Direction.RIGHT)));
179+
180+
EntityWithDirections reloaded = repositoryWithDirections.findById(saved.id).orElseThrow();
181+
182+
assertThat(reloaded).isEqualTo(saved);
183+
}
184+
165185
interface EntityWithStringyBigDecimalRepository extends CrudRepository<EntityWithStringyBigDecimal, CustomId> {
166186

167187
@Query("SELECT * FROM ENTITY_WITH_STRINGY_BIG_DECIMAL WHERE DIRECTION IN (:types)")
@@ -171,6 +191,8 @@ interface EntityWithStringyBigDecimalRepository extends CrudRepository<EntityWit
171191
List<EntityWithStringyBigDecimal> findByEnumType(Direction type);
172192
}
173193

194+
interface EntityWithDirectionsRepository extends CrudRepository<EntityWithDirections, Long> {}
195+
174196
private static class EntityWithStringyBigDecimal {
175197

176198
@Id CustomId id;
@@ -194,6 +216,9 @@ private static class OtherEntity {
194216
Date created;
195217
}
196218

219+
record EntityWithDirections(@Id Long id, List<Direction> directions) {
220+
}
221+
197222
enum Direction {
198223
LEFT, CENTER, RIGHT
199224
}
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10), DIRECTION INTEGER);
22
CREATE TABLE OTHER_ENTITY ( ID IDENTITY PRIMARY KEY, CREATED DATE, ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER);
3+
CREATE TABLE ENTITY_WITH_DIRECTIONS ( ID IDENTITY PRIMARY KEY, DIRECTIONS INTEGER ARRAY);
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10), DIRECTION INTEGER);
22
CREATE TABLE OTHER_ENTITY ( ID IDENTITY PRIMARY KEY, CREATED DATE, ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER);
3+
CREATE TABLE ENTITY_WITH_DIRECTIONS ( ID IDENTITY PRIMARY KEY, DIRECTIONS INTEGER ARRAY);
34

Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id SERIAL PRIMARY KEY, Stringy_number DECIMAL(20,10), DIRECTION INTEGER);
22
CREATE TABLE OTHER_ENTITY ( ID SERIAL PRIMARY KEY, CREATED DATE, ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER);
3+
CREATE TABLE ENTITY_WITH_DIRECTIONS ( ID SERIAL PRIMARY KEY, DIRECTIONS INTEGER ARRAY);

spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -777,14 +777,35 @@ private Object writeCollection(Iterable<?> value, TypeInformation<?> type) {
777777
}
778778

779779
for (Object o : value) {
780-
mapped.add(writeValue(o, component));
780+
mapped.add(unwrap(writeValue(o, component)));
781781
}
782782

783783
if (type.getType().isInstance(mapped) || !type.isCollectionLike()) {
784784
return mapped;
785785
}
786786

787-
return getConversionService().convert(mapped, type.getType());
787+
// if we succeeded converting the members of the collection, we actually ignore the fallback targetType since that
788+
// was derived without considering custom conversions.
789+
Class<?> targetType = type.getType();
790+
if (!mapped.isEmpty()) {
791+
792+
Class<?> targetComponentType = mapped.get(0).getClass();
793+
targetType = Array.newInstance(targetComponentType, 0).getClass();
794+
}
795+
return getConversionService().convert(mapped, targetType);
796+
}
797+
798+
/**
799+
* Unwraps technology specific wrappers. Custom conversions may choose to return a wrapper class that contains additional information for the technology driver.
800+
* These wrappers can't be used as members of a collection, therefore we may have to unwrap the values.
801+
*
802+
* This method allows technology specific implemenations to provide such an unwrapping mechanism.
803+
*
804+
* @param convertedValue a value that might need unwrapping.
805+
*/
806+
@Nullable
807+
protected Object unwrap(@Nullable Object convertedValue) {
808+
return convertedValue;
788809
}
789810

790811
static Predicate<RelationalPersistentProperty> isConstructorArgument(PersistentEntity<?, ?> entity) {

0 commit comments

Comments
 (0)