diff --git a/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java b/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java index 3ae56f702..ac528ad07 100644 --- a/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java @@ -27,15 +27,15 @@ import java.util.stream.StreamSupport; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.NonTransientDataAccessException; -import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.jdbc.support.JdbcUtil; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.PropertyPath; -import org.springframework.data.mapping.model.ConvertingPropertyAccessor; +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.ClassTypeInformation; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -48,6 +48,7 @@ import org.springframework.util.Assert; * The default {@link DataAccessStrategy} is to generate SQL statements based on meta data from the entity. * * @author Jens Schauder + * @author Mark Paluch * @since 1.0 */ @RequiredArgsConstructor @@ -59,8 +60,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { private final @NonNull SqlGeneratorSource sqlGeneratorSource; private final @NonNull RelationalMappingContext context; + private final @NonNull RelationalConverter converter; private final @NonNull NamedParameterJdbcOperations operations; - private final @NonNull EntityInstantiators instantiators; private final @NonNull DataAccessStrategy accessStrategy; /** @@ -68,12 +69,12 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { * Only suitable if this is the only access strategy in use. */ public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context, - NamedParameterJdbcOperations operations, EntityInstantiators instantiators) { + RelationalConverter converter, NamedParameterJdbcOperations operations) { this.sqlGeneratorSource = sqlGeneratorSource; this.operations = operations; this.context = context; - this.instantiators = instantiators; + this.converter = converter; this.accessStrategy = this; } @@ -92,7 +93,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { Assert.notNull(idProperty, "Since we have a non-null idValue, we must have an idProperty as well."); - additionalParameters.put(idProperty.getColumnName(), convert(idValue, idProperty.getColumnType())); + additionalParameters.put(idProperty.getColumnName(), + converter.writeValue(idValue, ClassTypeInformation.from(idProperty.getColumnType()))); } additionalParameters.forEach(parameterSource::addValue); @@ -231,7 +233,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { MapSqlParameterSource parameter = new MapSqlParameterSource( // "ids", // StreamSupport.stream(ids.spliterator(), false) // - .map(id -> convert(id, targetType)) // + .map(id -> converter.writeValue(id, ClassTypeInformation.from(targetType))) // .collect(Collectors.toList()) // ); @@ -281,11 +283,15 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { MapSqlParameterSource parameters = new MapSqlParameterSource(); + PersistentPropertyAccessor propertyAccessor = persistentEntity.getPropertyAccessor(instance); + persistentEntity.doWithProperties((PropertyHandler) property -> { + if (!property.isEntity()) { - Object value = persistentEntity.getPropertyAccessor(instance).getProperty(property); - Object convertedValue = convert(value, property.getColumnType()); + Object value = propertyAccessor.getProperty(property); + + Object convertedValue = converter.writeValue(value, ClassTypeInformation.from(property.getColumnType())); parameters.addValue(property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType())); } }); @@ -318,12 +324,10 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { getIdFromHolder(holder, persistentEntity).ifPresent(it -> { - PersistentPropertyAccessor accessor = persistentEntity.getPropertyAccessor(instance); - ConvertingPropertyAccessor convertingPropertyAccessor = new ConvertingPropertyAccessor(accessor, - context.getConversions()); + PersistentPropertyAccessor accessor = converter.getPropertyAccessor(persistentEntity, instance); RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty(); - convertingPropertyAccessor.setProperty(idProperty, it); + accessor.setProperty(idProperty, it); }); } catch (NonTransientDataAccessException e) { @@ -344,7 +348,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { } public EntityRowMapper getEntityRowMapper(Class domainType) { - return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context, instantiators, accessStrategy); + return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context, converter, accessStrategy); } @SuppressWarnings("unchecked") @@ -360,7 +364,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { private MapSqlParameterSource createIdParameterSource(Object id, Class domainType) { Class columnType = getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType(); - return new MapSqlParameterSource("id", convert(id, columnType)); + return new MapSqlParameterSource("id", converter.writeValue(id, ClassTypeInformation.from(columnType))); } @SuppressWarnings("unchecked") @@ -368,20 +372,6 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { return (RelationalPersistentEntity) context.getRequiredPersistentEntity(domainType); } - @Nullable - private V convert(@Nullable Object from, Class to) { - - if (from == null) { - return null; - } - - RelationalPersistentEntity persistentEntity = context.getPersistentEntity(from.getClass()); - - Object id = persistentEntity == null ? null : persistentEntity.getIdentifierAccessor(from).getIdentifier(); - - return context.getConversions().convert(id == null ? from : id, to); - } - private SqlGenerator sql(Class domainType) { return sqlGeneratorSource.getSqlGenerator(domainType); } diff --git a/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java index 152fd2f78..b06878a8a 100644 --- a/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java @@ -22,15 +22,13 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; -import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; -import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PreferredConstructor.Parameter; -import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.data.mapping.model.ParameterValueProvider; +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; @@ -39,9 +37,8 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** - * Maps a {@link ResultSet} to an entity of type {@code T}, including entities referenced. - * - * This {@link RowMapper} might trigger additional SQL statements in order to load other members of the same aggregate. + * Maps a {@link ResultSet} to an entity of type {@code T}, including entities referenced. This {@link RowMapper} might + * trigger additional SQL statements in order to load other members of the same aggregate. * * @author Jens Schauder * @author Oliver Gierke @@ -54,19 +51,17 @@ public class EntityRowMapper implements RowMapper { private final RelationalPersistentEntity entity; - private final ConversionService conversions; + private final RelationalConverter converter; private final RelationalMappingContext context; private final DataAccessStrategy accessStrategy; private final RelationalPersistentProperty idProperty; - private final EntityInstantiators instantiators; - public EntityRowMapper(RelationalPersistentEntity entity, RelationalMappingContext context, EntityInstantiators instantiators, - DataAccessStrategy accessStrategy) { + public EntityRowMapper(RelationalPersistentEntity entity, RelationalMappingContext context, + RelationalConverter converter, DataAccessStrategy accessStrategy) { this.entity = entity; - this.conversions = context.getConversions(); + this.converter = converter; this.context = context; - this.instantiators = instantiators; this.accessStrategy = accessStrategy; this.idProperty = entity.getIdProperty(); } @@ -80,8 +75,7 @@ public class EntityRowMapper implements RowMapper { T result = createInstance(entity, resultSet, ""); - ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(result), - conversions); + PersistentPropertyAccessor propertyAccessor = converter.getPropertyAccessor(entity, result); Object id = idProperty == null ? null : readFrom(resultSet, idProperty, ""); @@ -118,7 +112,7 @@ public class EntityRowMapper implements RowMapper { return readEntityFrom(resultSet, property); } - return resultSet.getObject(prefix + property.getColumnName()); + return converter.readValue(resultSet.getObject(prefix + property.getColumnName()), property.getTypeInformation()); } catch (SQLException o_O) { throw new MappingException(String.format("Could not read property %s from result set!", property), o_O); @@ -138,23 +132,19 @@ public class EntityRowMapper implements RowMapper { return null; } - S instance = - createInstance(entity, rs, prefix); + S instance = createInstance(entity, rs, prefix); - PersistentPropertyAccessor accessor = entity.getPropertyAccessor(instance); - ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(accessor, conversions); + PersistentPropertyAccessor accessor = converter.getPropertyAccessor(entity, instance); for (RelationalPersistentProperty p : entity) { - propertyAccessor.setProperty(p, readFrom(rs, p, prefix)); + accessor.setProperty(p, readFrom(rs, p, prefix)); } return instance; } private S createInstance(RelationalPersistentEntity entity, ResultSet rs, String prefix) { - - return instantiators.getInstantiatorFor(entity) // - .createInstance(entity, new ResultSetParameterValueProvider(rs, entity, conversions, prefix)); + return converter.createInstance(entity, new ResultSetParameterValueProvider(rs, entity, prefix)); } @RequiredArgsConstructor @@ -162,13 +152,13 @@ public class EntityRowMapper implements RowMapper { @NonNull private final ResultSet resultSet; @NonNull private final RelationalPersistentEntity entity; - @NonNull private final ConversionService conversionService; @NonNull private final String prefix; /* * (non-Javadoc) * @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter) */ + @SuppressWarnings("unchecked") @Override public T getParameterValue(Parameter parameter) { @@ -177,7 +167,7 @@ public class EntityRowMapper implements RowMapper { String column = prefix + entity.getRequiredPersistentProperty(parameterName).getColumnName(); try { - return conversionService.convert(resultSet.getObject(column), parameter.getType().getType()); + return (T) resultSet.getObject(column); } catch (SQLException o_O) { throw new MappingException(String.format("Couldn't read column %s from ResultSet.", column), o_O); } diff --git a/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java b/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java new file mode 100644 index 000000000..8d241618f --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java @@ -0,0 +1,59 @@ +/* + * 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.jdbc.core.convert; + +import java.util.Collections; +import java.util.List; + +import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes; + +/** + * Value object to capture custom conversion. {@link JdbcCustomConversions} also act as factory for + * {@link org.springframework.data.mapping.model.SimpleTypeHolder} + * + * @author Mark Paluch + * @see org.springframework.data.convert.CustomConversions + * @see org.springframework.data.mapping.model.SimpleTypeHolder + * @see JdbcSimpleTypes + */ +public class JdbcCustomConversions extends org.springframework.data.convert.CustomConversions { + + private static final StoreConversions STORE_CONVERSIONS; + private static final List STORE_CONVERTERS; + + static { + + STORE_CONVERTERS = Collections.emptyList(); + STORE_CONVERSIONS = StoreConversions.of(JdbcSimpleTypes.HOLDER, STORE_CONVERTERS); + } + + /** + * Creates an empty {@link JdbcCustomConversions} object. + */ + public JdbcCustomConversions() { + this(Collections.emptyList()); + } + + /** + * Create a new {@link JdbcCustomConversions} instance registering the given converters. + * + * @param converters must not be {@literal null}. + */ + public JdbcCustomConversions(List converters) { + super(STORE_CONVERSIONS, converters); + } + +} diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcSimpleTypes.java b/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcSimpleTypes.java new file mode 100644 index 000000000..7c0d7386c --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcSimpleTypes.java @@ -0,0 +1,77 @@ +/* + * 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.jdbc.core.mapping; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.RowId; +import java.sql.Struct; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import org.springframework.data.mapping.model.SimpleTypeHolder; + +/** + * Simple constant holder for a {@link SimpleTypeHolder} enriched with specific simple types for relational database + * access. + * + * @author Mark Paluch + */ +public abstract class JdbcSimpleTypes { + + public static final Set> AUTOGENERATED_ID_TYPES; + + static { + + Set> classes = new HashSet<>(); + classes.add(Long.class); + classes.add(String.class); + classes.add(BigInteger.class); + classes.add(BigDecimal.class); + classes.add(UUID.class); + AUTOGENERATED_ID_TYPES = Collections.unmodifiableSet(classes); + + Set> simpleTypes = new HashSet<>(); + simpleTypes.add(BigDecimal.class); + simpleTypes.add(BigInteger.class); + simpleTypes.add(Array.class); + simpleTypes.add(Clob.class); + simpleTypes.add(Blob.class); + simpleTypes.add(java.sql.Date.class); + simpleTypes.add(NClob.class); + simpleTypes.add(Ref.class); + simpleTypes.add(RowId.class); + simpleTypes.add(Struct.class); + simpleTypes.add(Time.class); + simpleTypes.add(Timestamp.class); + + JDBC_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes); + } + + private static final Set> JDBC_SIMPLE_TYPES; + public static final SimpleTypeHolder HOLDER = new SimpleTypeHolder(JDBC_SIMPLE_TYPES, true); + + private JdbcSimpleTypes() {} +} diff --git a/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java b/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java index 08b36769a..51ea83981 100644 --- a/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java @@ -29,6 +29,7 @@ import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.DelegatingDataAccessStrategy; import org.springframework.data.jdbc.core.SqlGeneratorSource; import org.springframework.data.mapping.PropertyPath; +import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -40,12 +41,13 @@ import org.springframework.util.Assert; * "Mapper". This is then followed by the method name separated by a dot. For methods taking a {@link PropertyPath} as * argument, the relevant entity is that of the root of the path, and the path itself gets as dot separated String * appended to the statement name. Each statement gets an instance of {@link MyBatisContext}, which at least has the - * entityType set. For methods taking a {@link PropertyPath} the entityTyoe if the context is set to the class of the + * entityType set. For methods taking a {@link PropertyPath} the entityType if the context is set to the class of the * leaf type. * * @author Jens Schauder * @author Kazuki Shimizu * @author Oliver Gierke + * @author Mark Paluch * @since 1.0 */ public class MyBatisDataAccessStrategy implements DataAccessStrategy { @@ -58,8 +60,9 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy { * uses a {@link DefaultDataAccessStrategy} */ public static DataAccessStrategy createCombinedAccessStrategy(RelationalMappingContext context, + RelationalConverter converter, NamedParameterJdbcOperations operations, SqlSession sqlSession) { - return createCombinedAccessStrategy(context, new EntityInstantiators(), operations, sqlSession, + return createCombinedAccessStrategy(context, converter, operations, sqlSession, NamespaceStrategy.DEFAULT_INSTANCE); } @@ -68,7 +71,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy { * uses a {@link DefaultDataAccessStrategy} */ public static DataAccessStrategy createCombinedAccessStrategy(RelationalMappingContext context, - EntityInstantiators instantiators, NamedParameterJdbcOperations operations, SqlSession sqlSession, + RelationalConverter converter, NamedParameterJdbcOperations operations, SqlSession sqlSession, NamespaceStrategy namespaceStrategy) { // the DefaultDataAccessStrategy needs a reference to the returned DataAccessStrategy. This creates a dependency @@ -85,8 +88,8 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy { DefaultDataAccessStrategy defaultDataAccessStrategy = new DefaultDataAccessStrategy( // sqlGeneratorSource, // context, // + converter, // operations, // - instantiators, // cascadingDataAccessStrategy // ); @@ -113,7 +116,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy { /** * Set a NamespaceStrategy to be used. - * + * * @param namespaceStrategy Must be non {@literal null} */ public void setNamespaceStrategy(NamespaceStrategy namespaceStrategy) { diff --git a/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java index 82171e6df..17a530387 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java +++ b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java @@ -19,15 +19,20 @@ import java.util.Optional; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.relational.core.mapping.ConversionCustomizer; -import org.springframework.data.relational.core.mapping.RelationalMappingContext; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.CustomConversions; +import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; +import org.springframework.data.relational.core.conversion.BasicRelationalConverter; +import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.NamingStrategy; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; /** * Beans that must be registered for Spring Data JDBC to work. * * @author Greg Turnquist * @author Jens Schauder + * @author Mark Paluch * @since 1.0 */ @Configuration @@ -35,9 +40,30 @@ public class JdbcConfiguration { @Bean RelationalMappingContext jdbcMappingContext(Optional namingStrategy, - Optional conversionCustomizer) { + CustomConversions customConversions) { + + RelationalMappingContext mappingContext = new RelationalMappingContext( + namingStrategy.orElse(NamingStrategy.INSTANCE)); + mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder()); + + return mappingContext; + } - return new RelationalMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE), - conversionCustomizer.orElse(ConversionCustomizer.NONE)); + @Bean + RelationalConverter relationalConverter(RelationalMappingContext mappingContext, + CustomConversions customConversions) { + return new BasicRelationalConverter(mappingContext, customConversions); + } + + /** + * Register custom {@link Converter}s in a {@link CustomConversions} object if required. These + * {@link CustomConversions} will be registered with the {@link #jdbcMappingContext()}. Returns an empty + * {@link JdbcCustomConversions} instance by default. + * + * @return must not be {@literal null}. + */ + @Bean + CustomConversions jdbcCustomConversions() { + return new JdbcCustomConversions(); } } diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java index b4d88fb86..c05d2dc02 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java @@ -17,13 +17,13 @@ package org.springframework.data.jdbc.repository.support; import java.lang.reflect.Method; -import org.springframework.core.convert.ConversionService; -import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.EntityRowMapper; import org.springframework.data.jdbc.repository.RowMapperMap; import org.springframework.data.projection.ProjectionFactory; +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.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.QueryLookupStrategy; @@ -44,33 +44,32 @@ import org.springframework.util.Assert; class JdbcQueryLookupStrategy implements QueryLookupStrategy { private final RelationalMappingContext context; - private final EntityInstantiators instantiators; + private final RelationalConverter converter; private final DataAccessStrategy accessStrategy; private final RowMapperMap rowMapperMap; private final NamedParameterJdbcOperations operations; - private final ConversionService conversionService; - /** - * Creates a new {@link JdbcQueryLookupStrategy} for the given {@link RelationalMappingContext}, {@link DataAccessStrategy} - * and {@link RowMapperMap}. + * Creates a new {@link JdbcQueryLookupStrategy} for the given {@link RelationalMappingContext}, + * {@link DataAccessStrategy} and {@link RowMapperMap}. * * @param context must not be {@literal null}. + * @param converter must not be {@literal null}. * @param accessStrategy must not be {@literal null}. * @param rowMapperMap must not be {@literal null}. */ - JdbcQueryLookupStrategy(RelationalMappingContext context, EntityInstantiators instantiators, + JdbcQueryLookupStrategy(RelationalMappingContext context, RelationalConverter converter, DataAccessStrategy accessStrategy, RowMapperMap rowMapperMap, NamedParameterJdbcOperations operations) { - Assert.notNull(context, "JdbcMappingContext must not be null!"); + Assert.notNull(context, "RelationalMappingContext must not be null!"); + Assert.notNull(converter, "RelationalConverter must not be null!"); Assert.notNull(accessStrategy, "DataAccessStrategy must not be null!"); Assert.notNull(rowMapperMap, "RowMapperMap must not be null!"); this.context = context; - this.instantiators = instantiators; + this.converter = converter; this.accessStrategy = accessStrategy; this.rowMapperMap = rowMapperMap; - this.conversionService = context.getConversions(); this.operations = operations; } @@ -93,9 +92,13 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy { Class returnedObjectType = queryMethod.getReturnedObjectType(); - return context.getSimpleTypeHolder().isSimpleType(returnedObjectType) - ? SingleColumnRowMapper.newInstance(returnedObjectType, conversionService) - : determineDefaultRowMapper(queryMethod); + RelationalPersistentEntity persistentEntity = context.getPersistentEntity(returnedObjectType); + + if (persistentEntity == null) { + return SingleColumnRowMapper.newInstance(returnedObjectType, converter.getConversionService()); + } + + return determineDefaultRowMapper(queryMethod); } private RowMapper determineDefaultRowMapper(JdbcQueryMethod queryMethod) { @@ -108,7 +111,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy { ? new EntityRowMapper<>( // context.getRequiredPersistentEntity(domainType), // context, // - instantiators, // + converter, // accessStrategy) // : typeMappedRowMapper; } diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index 539a1cb12..9a72eb449 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -18,10 +18,10 @@ package org.springframework.data.jdbc.repository.support; import java.util.Optional; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.JdbcAggregateTemplate; import org.springframework.data.jdbc.repository.RowMapperMap; +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.repository.core.EntityInformation; @@ -41,36 +41,40 @@ import org.springframework.util.Assert; * @author Jens Schauder * @author Greg Turnquist * @author Christoph Strobl + * @author Mark Paluch * @since 1.0 */ public class JdbcRepositoryFactory extends RepositoryFactorySupport { private final RelationalMappingContext context; + private final RelationalConverter converter; private final ApplicationEventPublisher publisher; private final DataAccessStrategy accessStrategy; private final NamedParameterJdbcOperations operations; private RowMapperMap rowMapperMap = RowMapperMap.EMPTY; - private EntityInstantiators instantiators = new EntityInstantiators(); /** - * Creates a new {@link JdbcRepositoryFactory} for the given {@link DataAccessStrategy}, {@link RelationalMappingContext} - * and {@link ApplicationEventPublisher}. - * + * Creates a new {@link JdbcRepositoryFactory} for the given {@link DataAccessStrategy}, + * {@link RelationalMappingContext} and {@link ApplicationEventPublisher}. + * * @param dataAccessStrategy must not be {@literal null}. * @param context must not be {@literal null}. + * @param converter must not be {@literal null}. * @param publisher must not be {@literal null}. * @param operations must not be {@literal null}. */ public JdbcRepositoryFactory(DataAccessStrategy dataAccessStrategy, RelationalMappingContext context, - ApplicationEventPublisher publisher, NamedParameterJdbcOperations operations) { + RelationalConverter converter, ApplicationEventPublisher publisher, NamedParameterJdbcOperations operations) { Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null!"); - Assert.notNull(context, "JdbcMappingContext must not be null!"); + Assert.notNull(context, "RelationalMappingContext must not be null!"); + Assert.notNull(converter, "RelationalConverter must not be null!"); Assert.notNull(publisher, "ApplicationEventPublisher must not be null!"); this.publisher = publisher; this.context = context; + this.converter = converter; this.accessStrategy = dataAccessStrategy; this.operations = operations; } @@ -85,18 +89,6 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport { this.rowMapperMap = rowMapperMap; } - /** - * Set the {@link EntityInstantiators} used for instantiating entity instances. - * - * @param instantiators Must not be {@code null}. - */ - public void setEntityInstantiators(EntityInstantiators instantiators) { - - Assert.notNull(instantiators, "EntityInstantiators must not be null."); - - this.instantiators = instantiators; - } - @SuppressWarnings("unchecked") @Override public EntityInformation getEntityInformation(Class aClass) { @@ -146,6 +138,6 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport { throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key)); } - return Optional.of(new JdbcQueryLookupStrategy(context, instantiators, accessStrategy, rowMapperMap, operations)); + return Optional.of(new JdbcQueryLookupStrategy(context, converter, accessStrategy, rowMapperMap, operations)); } } diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java index 97351390a..cd44d1283 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java @@ -20,11 +20,11 @@ import java.io.Serializable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.SqlGeneratorSource; import org.springframework.data.jdbc.repository.RowMapperMap; +import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactorySupport; @@ -47,14 +47,14 @@ public class JdbcRepositoryFactoryBean, S, ID extend private ApplicationEventPublisher publisher; private RelationalMappingContext mappingContext; + private RelationalConverter converter; private DataAccessStrategy dataAccessStrategy; private RowMapperMap rowMapperMap = RowMapperMap.EMPTY; private NamedParameterJdbcOperations operations; - private EntityInstantiators instantiators = new EntityInstantiators(); /** * Creates a new {@link JdbcRepositoryFactoryBean} for the given repository interface. - * + * * @param repositoryInterface must not be {@literal null}. */ JdbcRepositoryFactoryBean(Class repositoryInterface) { @@ -80,7 +80,7 @@ public class JdbcRepositoryFactoryBean, S, ID extend protected RepositoryFactorySupport doCreateRepositoryFactory() { JdbcRepositoryFactory jdbcRepositoryFactory = new JdbcRepositoryFactory(dataAccessStrategy, mappingContext, - publisher, operations); + converter, publisher, operations); jdbcRepositoryFactory.setRowMapperMap(rowMapperMap); return jdbcRepositoryFactory; @@ -115,9 +115,9 @@ public class JdbcRepositoryFactoryBean, S, ID extend this.operations = operations; } - @Autowired(required = false) - public void setInstantiators(EntityInstantiators instantiators) { - this.instantiators = instantiators; + @Autowired + public void setConverter(RelationalConverter converter) { + this.converter = converter; } /* @@ -128,22 +128,19 @@ public class JdbcRepositoryFactoryBean, S, ID extend public void afterPropertiesSet() { Assert.state(this.mappingContext != null, "MappingContext is required and must not be null!"); + Assert.state(this.converter != null, "RelationalConverter is required and must not be null!"); if (dataAccessStrategy == null) { SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(mappingContext); - this.dataAccessStrategy = new DefaultDataAccessStrategy(sqlGeneratorSource, mappingContext, operations, - instantiators); + this.dataAccessStrategy = new DefaultDataAccessStrategy(sqlGeneratorSource, mappingContext, converter, + operations); } if (rowMapperMap == null) { this.rowMapperMap = RowMapperMap.EMPTY; } - if (instantiators == null) { - this.instantiators = new EntityInstantiators(); - } - super.afterPropertiesSet(); } } diff --git a/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java b/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java new file mode 100644 index 000000000..8b3fc9514 --- /dev/null +++ b/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java @@ -0,0 +1,256 @@ +/* + * 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.relational.core.conversion; + +import lombok.RequiredArgsConstructor; + +import java.util.Collections; +import java.util.Optional; + +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.ConfigurableConversionService; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.data.convert.CustomConversions; +import org.springframework.data.convert.CustomConversions.StoreConversions; +import org.springframework.data.convert.EntityInstantiators; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.ConvertingPropertyAccessor; +import org.springframework.data.mapping.model.ParameterValueProvider; +import org.springframework.data.mapping.model.SimpleTypeHolder; +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; + +/** + * {@link RelationalConverter} that uses a {@link MappingContext} to apply basic conversion of relational values to + * property values. + *

+ * Conversion is configurable by providing a customized {@link CustomConversions}. + * + * @author Mark Paluch + * @see MappingContext + * @see SimpleTypeHolder + * @see CustomConversions + */ +public class BasicRelationalConverter implements RelationalConverter { + + private final MappingContext, RelationalPersistentProperty> context; + private final ConfigurableConversionService conversionService; + private final EntityInstantiators entityInstantiators; + private final CustomConversions conversions; + + /** + * Creates a new {@link BasicRelationalConverter} given {@link MappingContext}. + * + * @param context must not be {@literal null}. org.springframework.data.jdbc.core.DefaultDataAccessStrategyUnitTests + */ + public BasicRelationalConverter( + MappingContext, ? extends RelationalPersistentProperty> context) { + this(context, new CustomConversions(StoreConversions.NONE, Collections.emptyList()), new DefaultConversionService(), + new EntityInstantiators()); + } + + /** + * Creates a new {@link BasicRelationalConverter} given {@link MappingContext} and {@link CustomConversions}. + * + * @param context must not be {@literal null}. + * @param conversions must not be {@literal null}. + */ + public BasicRelationalConverter( + MappingContext, ? extends RelationalPersistentProperty> context, + CustomConversions conversions) { + this(context, conversions, new DefaultConversionService(), new EntityInstantiators()); + } + + @SuppressWarnings("unchecked") + private BasicRelationalConverter( + MappingContext, ? extends RelationalPersistentProperty> context, + CustomConversions conversions, ConfigurableConversionService conversionService, + EntityInstantiators entityInstantiators) { + + Assert.notNull(context, "MappingContext must not be null!"); + Assert.notNull(conversions, "CustomConversions must not be null!"); + + this.context = (MappingContext) context; + this.conversionService = conversionService; + this.entityInstantiators = entityInstantiators; + this.conversions = conversions; + + conversions.registerConvertersIn(this.conversionService); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.conversion.RelationalConverter#getConversionService() + */ + @Override + public ConversionService getConversionService() { + return conversionService; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.conversion.RelationalConverter#getMappingContext() + */ + @Override + public MappingContext, ? extends RelationalPersistentProperty> getMappingContext() { + return context; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.conversion.RelationalConverter#getPropertyAccessor(org.springframework.data.mapping.PersistentEntity, java.lang.Object) + */ + @Override + public PersistentPropertyAccessor getPropertyAccessor(PersistentEntity persistentEntity, T instance) { + + PersistentPropertyAccessor accessor = persistentEntity.getPropertyAccessor(instance); + return new ConvertingPropertyAccessor<>(accessor, conversionService); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.conversion.RelationalConverter#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider) + */ + @Override + public T createInstance(PersistentEntity entity, + ParameterValueProvider parameterValueProvider) { + + return entityInstantiators.getInstantiatorFor(entity) // + .createInstance(entity, new ConvertingParameterValueProvider<>(parameterValueProvider)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.conversion.RelationalConverter#readValue(java.lang.Object, org.springframework.data.util.TypeInformation) + */ + @Override + @Nullable + public Object readValue(@Nullable Object value, TypeInformation type) { + + if (null == value) { + return null; + } + + if (conversions.hasCustomReadTarget(value.getClass(), type.getType())) { + return conversionService.convert(value, type.getType()); + } else { + return getPotentiallyConvertedSimpleRead(value, type.getType()); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.conversion.RelationalConverter#writeValue(java.lang.Object, org.springframework.data.util.TypeInformation) + */ + @Override + @Nullable + public Object writeValue(@Nullable Object value, TypeInformation type) { + + if (value == null) { + return null; + } + + Class rawType = type.getType(); + RelationalPersistentEntity persistentEntity = context.getPersistentEntity(value.getClass()); + + if (persistentEntity != null) { + + Object id = persistentEntity.getIdentifierAccessor(value).getIdentifier(); + return writeValue(id, type); + } + + if (rawType.isInstance(value)) { + return getPotentiallyConvertedSimpleWrite(value); + } + + return conversionService.convert(value, rawType); + } + + /** + * Checks whether we have a custom conversion registered for the given value into an arbitrary simple JDBC type. + * Returns the converted value if so. If not, we perform special enum handling or simply return the value as is. + * + * @param value + * @return + */ + private Object getPotentiallyConvertedSimpleWrite(Object value) { + + Optional> customTarget = conversions.getCustomWriteTarget(value.getClass()); + + if (customTarget.isPresent()) { + return conversionService.convert(value, customTarget.get()); + } + + return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum) value).name() : value; + } + + /** + * Checks whether we have a custom conversion for the given simple object. Converts the given value if so, applies + * {@link Enum} handling or returns the value as is. + * + * @param value + * @param target must not be {@literal null}. + * @return + */ + @Nullable + @SuppressWarnings({ "rawtypes", "unchecked" }) + private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class target) { + + if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) { + return value; + } + + if (conversions.hasCustomReadTarget(value.getClass(), target)) { + return conversionService.convert(value, target); + } + + if (Enum.class.isAssignableFrom(target)) { + return Enum.valueOf((Class) target, value.toString()); + } + + return conversionService.convert(value, target); + } + + /** + * Converter-aware {@link ParameterValueProvider}. + * + * @param

+ * @author Mark Paluch + */ + @RequiredArgsConstructor + class ConvertingParameterValueProvider

> implements ParameterValueProvider

{ + + private final ParameterValueProvider

delegate; + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter) + */ + @Override + @SuppressWarnings("unchecked") + public T getParameterValue(Parameter parameter) { + return (T) readValue(delegate.getParameterValue(parameter), parameter.getType()); + } + } +} diff --git a/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java b/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java new file mode 100644 index 000000000..19aa676d1 --- /dev/null +++ b/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java @@ -0,0 +1,90 @@ +/* + * 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.relational.core.conversion; + +import org.springframework.core.convert.ConversionService; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.ParameterValueProvider; +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; + +/** + * A {@link RelationalConverter} is responsible for converting for values to the native relational representation and + * vice versa. + * + * @author Mark Paluch + */ +public interface RelationalConverter { + + /** + * Returns the underlying {@link MappingContext} used by the converter. + * + * @return never {@literal null} + */ + MappingContext, ? extends RelationalPersistentProperty> getMappingContext(); + + /** + * Returns the underlying {@link ConversionService} used by the converter. + * + * @return never {@literal null}. + */ + ConversionService getConversionService(); + + /** + * Create a new instance of {@link PersistentEntity} given {@link ParameterValueProvider} to obtain constructor + * properties. + * + * @param entity + * @param parameterValueProvider + * @param + * @return + */ + T createInstance(PersistentEntity entity, + ParameterValueProvider parameterValueProvider); + + /** + * Return a {@link PersistentPropertyAccessor} to access property values of the {@code instance}. + * + * @param persistentEntity + * @param instance + * @return + */ + PersistentPropertyAccessor getPropertyAccessor(PersistentEntity persistentEntity, T instance); + + /** + * Read a relational value into the desired {@link TypeInformation destination type}. + * + * @param value + * @param type + * @return + */ + @Nullable + Object readValue(@Nullable Object value, TypeInformation type); + + /** + * Write a property value into a relational type that can be stored natively. + * + * @param value + * @param type + * @return + */ + @Nullable + Object writeValue(@Nullable Object value, TypeInformation type); +} diff --git a/src/main/java/org/springframework/data/relational/core/mapping/ConversionCustomizer.java b/src/main/java/org/springframework/data/relational/core/mapping/ConversionCustomizer.java deleted file mode 100644 index 92679eb2f..000000000 --- a/src/main/java/org/springframework/data/relational/core/mapping/ConversionCustomizer.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2017-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.relational.core.mapping; - -import org.springframework.core.convert.support.GenericConversionService; - -/** - * Interface to register custom conversions. - * - * @author Jens Schauder - * @since 1.0 - */ -public interface ConversionCustomizer { - - /** - * Noop instance to be used as a default. - */ - ConversionCustomizer NONE = __ -> {}; - - /** - * Gets called in order to allow the customization of the {@link org.springframework.core.convert.ConversionService}. - * Typically used by registering additional conversions. - * - * @param conversions the conversions that get customized. - */ - void customize(GenericConversionService conversions); -} diff --git a/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java b/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java index 78a2bb6ec..76c56efec 100644 --- a/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java +++ b/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java @@ -15,22 +15,12 @@ */ package org.springframework.data.relational.core.mapping; -import static java.util.Arrays.*; - import lombok.Getter; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.time.temporal.Temporal; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.data.convert.Jsr310Converters; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.mapping.context.AbstractMappingContext; import org.springframework.data.mapping.context.MappingContext; @@ -47,60 +37,33 @@ import org.springframework.util.Assert; * @author Greg Turnquist * @author Kazuki Shimizu * @author Oliver Gierke + * @author Mark Paluch * @since 1.0 */ public class RelationalMappingContext extends AbstractMappingContext, RelationalPersistentProperty> { - private static final HashSet> CUSTOM_SIMPLE_TYPES = new HashSet<>(asList( // - BigDecimal.class, // - BigInteger.class, // - Temporal.class // - )); - @Getter private final NamingStrategy namingStrategy; - private final GenericConversionService conversions = getDefaultConversionService(); - @Getter private SimpleTypeHolder simpleTypeHolder; /** * Creates a new {@link RelationalMappingContext}. */ public RelationalMappingContext() { - this(NamingStrategy.INSTANCE, ConversionCustomizer.NONE); - } - - public RelationalMappingContext(NamingStrategy namingStrategy) { - this(namingStrategy, ConversionCustomizer.NONE); + this(NamingStrategy.INSTANCE); } /** - * Creates a new {@link RelationalMappingContext} using the given {@link NamingStrategy} and {@link ConversionCustomizer}. - * + * Creates a new {@link RelationalMappingContext} using the given {@link NamingStrategy}. + * * @param namingStrategy must not be {@literal null}. * @param customizer must not be {@literal null}. */ - public RelationalMappingContext(NamingStrategy namingStrategy, ConversionCustomizer customizer) { + public RelationalMappingContext(NamingStrategy namingStrategy) { Assert.notNull(namingStrategy, "NamingStrategy must not be null!"); - Assert.notNull(customizer, "ConversionCustomizer must not be null!"); this.namingStrategy = namingStrategy; - customizer.customize(conversions); - setSimpleTypeHolder(new SimpleTypeHolder(CUSTOM_SIMPLE_TYPES, true)); - } - - private static GenericConversionService getDefaultConversionService() { - - DefaultConversionService conversionService = new DefaultConversionService(); - Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter); - - return conversionService; - } - - @Override - public void setSimpleTypeHolder(SimpleTypeHolder simpleTypes) { - super.setSimpleTypeHolder(simpleTypes); - this.simpleTypeHolder = simpleTypes; + setSimpleTypeHolder(new SimpleTypeHolder(Collections.emptySet(), true)); } /** @@ -147,8 +110,4 @@ public class RelationalMappingContext extends AbstractMappingContext additionalParameters = new HashMap<>(); ArgumentCaptor paramSourceCaptor = ArgumentCaptor.forClass(SqlParameterSource.class); DefaultDataAccessStrategy accessStrategy = new DefaultDataAccessStrategy( // new SqlGeneratorSource(context), // context, // - jdbcOperations, // - new EntityInstantiators() // - ); + converter, // + jdbcOperations); @Test // DATAJDBC-146 public void additionalParameterForIdDoesNotLeadToDuplicateParameters() { @@ -83,10 +91,65 @@ public class DefaultDataAccessStrategyUnitTests { assertThat(paramSourceCaptor.getValue().getValue("id")).isEqualTo(ORIGINAL_ID); } + @Test // DATAJDBC-235 + public void considersConfiguredWriteConverter() { + + RelationalConverter converter = new BasicRelationalConverter(context, + new JdbcCustomConversions(Arrays.asList(BooleanToStringConverter.INSTANCE, StringToBooleanConverter.INSTANCE))); + + DefaultDataAccessStrategy accessStrategy = new DefaultDataAccessStrategy( // + new SqlGeneratorSource(context), // + context, // + converter, // + jdbcOperations); + + ArgumentCaptor sqlCaptor = ArgumentCaptor.forClass(String.class); + + EntityWithBoolean entity = new EntityWithBoolean(ORIGINAL_ID, true); + + accessStrategy.insert(entity, EntityWithBoolean.class, new HashMap<>()); + + verify(jdbcOperations).update(sqlCaptor.capture(), paramSourceCaptor.capture(), any(KeyHolder.class)); + + assertThat(sqlCaptor.getValue()) // + .contains("INSERT INTO entity_with_boolean (flag, id) VALUES (:flag, :id)"); + assertThat(paramSourceCaptor.getValue().getValue("id")).isEqualTo(ORIGINAL_ID); + assertThat(paramSourceCaptor.getValue().getValue("flag")).isEqualTo("T"); + } + @RequiredArgsConstructor private static class DummyEntity { @Id private final Long id; } + @AllArgsConstructor + private static class EntityWithBoolean { + + @Id Long id; + boolean flag; + } + + @WritingConverter + enum BooleanToStringConverter implements Converter { + + INSTANCE; + + @Override + public String convert(Boolean source) { + return source != null && source ? "T" : "F"; + } + } + + @ReadingConverter + enum StringToBooleanConverter implements Converter { + + INSTANCE; + + @Override + public Boolean convert(String source) { + return source != null && source.equalsIgnoreCase("T") ? Boolean.TRUE : Boolean.FALSE; + } + } + } diff --git a/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java index c1b14e0eb..ac77d8817 100644 --- a/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java @@ -38,21 +38,21 @@ import javax.naming.OperationNotSupportedException; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.annotation.Id; -import org.springframework.data.convert.EntityInstantiators; -import org.springframework.data.convert.Jsr310Converters; +import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; +import org.springframework.data.relational.core.conversion.BasicRelationalConverter; +import org.springframework.data.relational.core.conversion.RelationalConverter; +import org.springframework.data.relational.core.mapping.NamingStrategy; 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.relational.core.mapping.NamingStrategy; import org.springframework.util.Assert; /** * Tests the extraction of entities from a {@link ResultSet} by the {@link EntityRowMapper}. * * @author Jens Schauder + * @author Mark Paluch */ public class EntityRowMapperUnitTests { @@ -195,15 +195,12 @@ public class EntityRowMapperUnitTests { new SimpleEntry<>(2, new Trivial()) // ))).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_LIST), any(RelationalPersistentProperty.class)); - GenericConversionService conversionService = new GenericConversionService(); - conversionService.addConverter(new IterableOfEntryToMapConverter()); - DefaultConversionService.addDefaultConverters(conversionService); - Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter); + RelationalConverter converter = new BasicRelationalConverter(context, new JdbcCustomConversions()); return new EntityRowMapper<>( // (RelationalPersistentEntity) context.getRequiredPersistentEntity(type), // context, // - new EntityInstantiators(), // + converter, // accessStrategy // ); } diff --git a/src/test/java/org/springframework/data/jdbc/mapping/model/NamingStrategyUnitTests.java b/src/test/java/org/springframework/data/jdbc/mapping/model/NamingStrategyUnitTests.java index 9cee2c028..35a5dc3e6 100644 --- a/src/test/java/org/springframework/data/jdbc/mapping/model/NamingStrategyUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/mapping/model/NamingStrategyUnitTests.java @@ -16,7 +16,6 @@ package org.springframework.data.jdbc.mapping.model; import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; import lombok.Data; @@ -25,23 +24,23 @@ import java.util.List; import org.junit.Test; import org.springframework.data.annotation.Id; -import org.springframework.data.relational.core.mapping.ConversionCustomizer; +import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; -import org.springframework.data.relational.core.mapping.NamingStrategy; /** * Unit tests for the default {@link NamingStrategy}. * * @author Kazuki Shimizu * @author Jens Schauder + * @author Mark Paluch */ public class NamingStrategyUnitTests { private final NamingStrategy target = NamingStrategy.INSTANCE; private final RelationalPersistentEntity persistentEntity = // - new RelationalMappingContext(target, mock(ConversionCustomizer.class)).getRequiredPersistentEntity(DummyEntity.class); + new RelationalMappingContext(target).getRequiredPersistentEntity(DummyEntity.class); @Test // DATAJDBC-184 public void getTableName() { diff --git a/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java index 0373911e7..b9d24bf78 100644 --- a/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java @@ -33,6 +33,7 @@ import org.springframework.context.annotation.Import; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -48,6 +49,7 @@ import org.springframework.transaction.annotation.Transactional; * * @author Jens Schauder * @author Greg Turnquist + * @author Mark Paluch */ @ContextConfiguration @ActiveProfiles("hsql") @@ -86,8 +88,10 @@ public class MyBatisHsqlIntegrationTests { } @Bean - DataAccessStrategy dataAccessStrategy(RelationalMappingContext context, SqlSession sqlSession, EmbeddedDatabase db) { - return MyBatisDataAccessStrategy.createCombinedAccessStrategy(context, new NamedParameterJdbcTemplate(db), + DataAccessStrategy dataAccessStrategy(RelationalMappingContext context, RelationalConverter converter, + SqlSession sqlSession, EmbeddedDatabase db) { + return MyBatisDataAccessStrategy.createCombinedAccessStrategy(context, converter, + new NamedParameterJdbcTemplate(db), sqlSession); } } diff --git a/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java index 4ef5e98a7..deab95c37 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java @@ -36,11 +36,13 @@ import org.junit.Test; import org.mockito.stubbing.Answer; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.annotation.Id; -import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.SqlGeneratorSource; +import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository; +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.event.AfterDeleteEvent; import org.springframework.data.relational.core.mapping.event.AfterLoadEvent; @@ -72,14 +74,16 @@ public class SimpleJdbcRepositoryEventsUnitTests { public void before() { RelationalMappingContext context = new RelationalMappingContext(); + RelationalConverter converter = new BasicRelationalConverter(context, new JdbcCustomConversions()); NamedParameterJdbcOperations operations = createIdGeneratingOperations(); SqlGeneratorSource generatorSource = new SqlGeneratorSource(context); this.dataAccessStrategy = spy( - new DefaultDataAccessStrategy(generatorSource, context, operations, new EntityInstantiators())); + new DefaultDataAccessStrategy(generatorSource, context, converter, operations)); - JdbcRepositoryFactory factory = new JdbcRepositoryFactory(dataAccessStrategy, context, publisher, operations); + JdbcRepositoryFactory factory = new JdbcRepositoryFactory(dataAccessStrategy, context, converter, publisher, + operations); this.repository = factory.getRepository(DummyEntityRepository.class); } diff --git a/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java b/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java index 065060167..ea8db06ce 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java @@ -25,12 +25,13 @@ import java.text.NumberFormat; import org.junit.Before; import org.junit.Test; -import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.repository.RowMapperMap; import org.springframework.data.jdbc.repository.config.ConfigurableRowMapperMap; import org.springframework.data.jdbc.repository.query.Query; import org.springframework.data.projection.ProjectionFactory; +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.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryMetadata; @@ -44,10 +45,12 @@ import org.springframework.jdbc.core.namedparam.SqlParameterSource; * * @author Jens Schauder * @author Oliver Gierke + * @author Mark Paluch */ public class JdbcQueryLookupStrategyUnitTests { RelationalMappingContext mappingContext = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS); + RelationalConverter converter = mock(BasicRelationalConverter.class); DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class); ProjectionFactory projectionFactory = mock(ProjectionFactory.class); RepositoryMetadata metadata; @@ -79,8 +82,8 @@ public class JdbcQueryLookupStrategyUnitTests { private RepositoryQuery getRepositoryQuery(String name, RowMapperMap rowMapperMap) { - JdbcQueryLookupStrategy queryLookupStrategy = new JdbcQueryLookupStrategy(mappingContext, new EntityInstantiators(), - accessStrategy, rowMapperMap, operations); + JdbcQueryLookupStrategy queryLookupStrategy = new JdbcQueryLookupStrategy(mappingContext, converter, accessStrategy, + rowMapperMap, operations); return queryLookupStrategy.resolveQuery(getMethod(name), metadata, projectionFactory, namedQueries); } diff --git a/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java b/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java index a034bab4b..1df85c738 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java @@ -29,6 +29,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.repository.RowMapperMap; +import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.CrudRepository; import org.springframework.test.util.ReflectionTestUtils; @@ -40,6 +41,7 @@ import org.springframework.test.util.ReflectionTestUtils; * @author Greg Turnquist * @author Christoph Strobl * @author Oliver Gierke + * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) public class JdbcRepositoryFactoryBeanUnitTests { @@ -65,6 +67,7 @@ public class JdbcRepositoryFactoryBeanUnitTests { factoryBean.setDataAccessStrategy(dataAccessStrategy); factoryBean.setMappingContext(mappingContext); + factoryBean.setConverter(new BasicRelationalConverter(mappingContext)); factoryBean.setApplicationEventPublisher(publisher); factoryBean.afterPropertiesSet(); @@ -89,6 +92,7 @@ public class JdbcRepositoryFactoryBeanUnitTests { public void afterPropertiesSetDefaultsNullablePropertiesCorrectly() { factoryBean.setMappingContext(mappingContext); + factoryBean.setConverter(new BasicRelationalConverter(mappingContext)); factoryBean.setApplicationEventPublisher(publisher); factoryBean.afterPropertiesSet(); diff --git a/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java index 47d08c3ce..1afdfbc23 100644 --- a/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java +++ b/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java @@ -25,14 +25,16 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.data.convert.EntityInstantiators; +import org.springframework.data.convert.CustomConversions; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.SqlGeneratorSource; +import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; -import org.springframework.data.relational.core.mapping.ConversionCustomizer; -import org.springframework.data.relational.core.mapping.RelationalMappingContext; +import org.springframework.data.relational.core.conversion.BasicRelationalConverter; +import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.NamingStrategy; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; @@ -43,6 +45,7 @@ import org.springframework.transaction.PlatformTransactionManager; * * @author Oliver Gierke * @author Jens Schauder + * @author Mark Paluch */ @Configuration @ComponentScan // To pick up configuration classes (per activated profile) @@ -53,11 +56,9 @@ public class TestConfiguration { @Autowired(required = false) SqlSessionFactory sqlSessionFactory; @Bean - JdbcRepositoryFactory jdbcRepositoryFactory(DataAccessStrategy dataAccessStrategy) { - - RelationalMappingContext context = new RelationalMappingContext(NamingStrategy.INSTANCE); - - return new JdbcRepositoryFactory(dataAccessStrategy, context, publisher, namedParameterJdbcTemplate()); + JdbcRepositoryFactory jdbcRepositoryFactory(DataAccessStrategy dataAccessStrategy, RelationalMappingContext context, + RelationalConverter converter) { + return new JdbcRepositoryFactory(dataAccessStrategy, context, converter, publisher, namedParameterJdbcTemplate()); } @Bean @@ -71,15 +72,28 @@ public class TestConfiguration { } @Bean - DataAccessStrategy defaultDataAccessStrategy(RelationalMappingContext context) { - return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, namedParameterJdbcTemplate(), new EntityInstantiators()); + DataAccessStrategy defaultDataAccessStrategy(RelationalMappingContext context, RelationalConverter converter) { + return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter, + namedParameterJdbcTemplate()); + } + + @Bean + RelationalMappingContext jdbcMappingContext(NamedParameterJdbcOperations template, + Optional namingStrategy, CustomConversions conversions) { + + RelationalMappingContext mappingContext = new RelationalMappingContext( + namingStrategy.orElse(NamingStrategy.INSTANCE)); + mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); + return mappingContext; } @Bean - RelationalMappingContext jdbcMappingContext(NamedParameterJdbcOperations template, Optional namingStrategy, - Optional conversionCustomizer) { + CustomConversions jdbcCustomConversions() { + return new JdbcCustomConversions(); + } - return new RelationalMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE), - conversionCustomizer.orElse(conversionService -> {})); + @Bean + RelationalConverter relationalConverter(RelationalMappingContext mappingContext, CustomConversions conversions) { + return new BasicRelationalConverter(mappingContext, conversions); } } diff --git a/src/test/java/org/springframework/data/relational/core/conversion/BasicRelationalConverterUnitTests.java b/src/test/java/org/springframework/data/relational/core/conversion/BasicRelationalConverterUnitTests.java new file mode 100644 index 000000000..cf0e8b2c8 --- /dev/null +++ b/src/test/java/org/springframework/data/relational/core/conversion/BasicRelationalConverterUnitTests.java @@ -0,0 +1,104 @@ +/* + * 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.relational.core.conversion; + +import static org.assertj.core.api.Assertions.*; + +import lombok.Data; +import lombok.Value; + +import org.junit.Test; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.data.mapping.model.ParameterValueProvider; +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.ClassTypeInformation; + +/** + * Unit tests for {@link BasicRelationalConverter}. + * + * @author Mark Paluch + */ +public class BasicRelationalConverterUnitTests { + + RelationalMappingContext context = new RelationalMappingContext(); + RelationalConverter converter = new BasicRelationalConverter(context); + + @Test // DATAJDBC-235 + @SuppressWarnings("unchecked") + public void shouldUseConvertingPropertyAccessor() { + + RelationalPersistentEntity entity = (RelationalPersistentEntity) context + .getRequiredPersistentEntity(MyEntity.class); + + MyEntity instance = new MyEntity(); + + PersistentPropertyAccessor accessor = converter.getPropertyAccessor(entity, instance); + RelationalPersistentProperty property = entity.getRequiredPersistentProperty("flag"); + accessor.setProperty(property, "1"); + + assertThat(instance.isFlag()).isTrue(); + } + + @Test // DATAJDBC-235 + public void shouldConvertEnumToString() { + + Object result = converter.writeValue(MyEnum.ON, ClassTypeInformation.from(String.class)); + + assertThat(result).isEqualTo("ON"); + } + + @Test // DATAJDBC-235 + public void shouldConvertStringToEnum() { + + Object result = converter.readValue("OFF", ClassTypeInformation.from(MyEnum.class)); + + assertThat(result).isEqualTo(MyEnum.OFF); + } + + @Test // DATAJDBC-235 + @SuppressWarnings("unchecked") + public void shouldCreateInstance() { + + RelationalPersistentEntity entity = (RelationalPersistentEntity) context + .getRequiredPersistentEntity(MyValue.class); + + MyValue result = converter.createInstance(entity, new ParameterValueProvider() { + @Override + public T getParameterValue(Parameter parameter) { + return (T) "bar"; + } + }); + + assertThat(result.getFoo()).isEqualTo("bar"); + } + + @Data + static class MyEntity { + boolean flag; + } + + @Value + static class MyValue { + final String foo; + } + + enum MyEnum { + ON, OFF; + } +}