Browse Source

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
pull/2110/head
Jens Schauder 5 months ago committed by Mark Paluch
parent
commit
3fec94ab46
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 21
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java
  2. 25
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java
  3. 1
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-h2.sql
  4. 1
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql
  5. 1
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql
  6. 25
      spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java

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

@ -255,6 +255,9 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements @@ -255,6 +255,9 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements
}
Class<?> componentType = convertedValue.getClass().getComponentType();
if (convertedValue.getClass().isArray()) {
if (componentType != byte.class && componentType != Byte.class) {
Object[] objectArray = requireObjectArray(convertedValue);
@ -268,6 +271,24 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements @@ -268,6 +271,24 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements
return JdbcValue.of(convertedValue, JDBCType.BINARY);
}
return JdbcValue.of(convertedValue, sqlType);
}
/**
* 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> R readAndResolve(TypeInformation<R> type, RowDocument source, Identifier identifier) {

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

@ -38,8 +38,10 @@ import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; @@ -38,8 +38,10 @@ import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
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 @@ public class JdbcRepositoryCustomConversionIntegrationTests { @@ -61,6 +63,11 @@ public class JdbcRepositoryCustomConversionIntegrationTests {
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 @@ public class JdbcRepositoryCustomConversionIntegrationTests { @@ -70,6 +77,7 @@ public class JdbcRepositoryCustomConversionIntegrationTests {
}
@Autowired EntityWithStringyBigDecimalRepository repository;
@Autowired EntityWithDirectionsRepository repositoryWithDirections;
/**
* In PostrgreSQL this fails if a simple converter like the following is used.
@ -162,6 +170,18 @@ public class JdbcRepositoryCustomConversionIntegrationTests { @@ -162,6 +170,18 @@ public class JdbcRepositoryCustomConversionIntegrationTests {
.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<EntityWithStringyBigDecimal, CustomId> {
@Query("SELECT * FROM ENTITY_WITH_STRINGY_BIG_DECIMAL WHERE DIRECTION IN (:types)")
@ -171,6 +191,8 @@ public class JdbcRepositoryCustomConversionIntegrationTests { @@ -171,6 +191,8 @@ public class JdbcRepositoryCustomConversionIntegrationTests {
List<EntityWithStringyBigDecimal> findByEnumType(Direction type);
}
interface EntityWithDirectionsRepository extends CrudRepository<EntityWithDirections, Long> {}
private static class EntityWithStringyBigDecimal {
@Id CustomId id;
@ -194,6 +216,9 @@ public class JdbcRepositoryCustomConversionIntegrationTests { @@ -194,6 +216,9 @@ public class JdbcRepositoryCustomConversionIntegrationTests {
Date created;
}
record EntityWithDirections(@Id Long id, List<Direction> directions) {
}
enum Direction {
LEFT, CENTER, RIGHT
}

1
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-h2.sql

@ -1,2 +1,3 @@ @@ -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);

1
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql

@ -1,3 +1,4 @@ @@ -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);

1
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql

@ -1,2 +1,3 @@ @@ -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);

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

@ -777,14 +777,35 @@ public class MappingRelationalConverter extends AbstractRelationalConverter @@ -777,14 +777,35 @@ public class MappingRelationalConverter extends AbstractRelationalConverter
}
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<RelationalPersistentProperty> isConstructorArgument(PersistentEntity<?, ?> entity) {

Loading…
Cancel
Save