Browse Source

DATAJDBC-235 - Add support for configurable conversion.

We now support configurable conversion by introducing CustomConversions and RelationalConverter. CustomConversions is a registry for converters that should be applied on a per-type basis for properties. CustomConversions is typically registered as bean and fed into RelationalMappingContext and the newly introduced RelationalConverter to consider simple types and conversion rules.

RelationalConverter with its implementation BasicRelationalConverter encapsulates conversion infrastructure such as EntityInstantiator, CustomConversions, and MappingContext that is required during relational value conversion. BasicRelationalConverter is responsible for simple value conversion and entity instantiation to pull related code together. It's not in full charge of row result to object mapping as this responsibility remains as part of DataAccessStrategy.

This change supersedes and removes ConversionCustomizer.
pull/79/head
Mark Paluch 8 years ago committed by Jens Schauder
parent
commit
fb858bf1b1
  1. 48
      src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java
  2. 40
      src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java
  3. 59
      src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java
  4. 77
      src/main/java/org/springframework/data/jdbc/core/mapping/JdbcSimpleTypes.java
  5. 11
      src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java
  6. 36
      src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java
  7. 33
      src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java
  8. 30
      src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java
  9. 21
      src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java
  10. 256
      src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java
  11. 90
      src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java
  12. 40
      src/main/java/org/springframework/data/relational/core/mapping/ConversionCustomizer.java
  13. 51
      src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java
  14. 71
      src/test/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategyUnitTests.java
  15. 17
      src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java
  16. 7
      src/test/java/org/springframework/data/jdbc/mapping/model/NamingStrategyUnitTests.java
  17. 8
      src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java
  18. 10
      src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java
  19. 9
      src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java
  20. 4
      src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java
  21. 42
      src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java
  22. 104
      src/test/java/org/springframework/data/relational/core/conversion/BasicRelationalConverterUnitTests.java

48
src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java

@ -27,15 +27,15 @@ import java.util.stream.StreamSupport; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -281,11 +283,15 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
MapSqlParameterSource parameters = new MapSqlParameterSource();
PersistentPropertyAccessor<S> propertyAccessor = persistentEntity.getPropertyAccessor(instance);
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) 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 { @@ -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<S> 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 { @@ -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 { @@ -360,7 +364,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> 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 { @@ -368,20 +372,6 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
return (RelationalPersistentEntity<S>) context.getRequiredPersistentEntity(domainType);
}
@Nullable
private <V> V convert(@Nullable Object from, Class<V> 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);
}

40
src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java

@ -22,15 +22,13 @@ import java.sql.ResultSet; @@ -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; @@ -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<T> implements RowMapper<T> { @@ -54,19 +51,17 @@ public class EntityRowMapper<T> implements RowMapper<T> {
private final RelationalPersistentEntity<T> 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<T> entity, RelationalMappingContext context, EntityInstantiators instantiators,
DataAccessStrategy accessStrategy) {
public EntityRowMapper(RelationalPersistentEntity<T> 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<T> implements RowMapper<T> { @@ -80,8 +75,7 @@ public class EntityRowMapper<T> implements RowMapper<T> {
T result = createInstance(entity, resultSet, "");
ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(result),
conversions);
PersistentPropertyAccessor<T> propertyAccessor = converter.getPropertyAccessor(entity, result);
Object id = idProperty == null ? null : readFrom(resultSet, idProperty, "");
@ -118,7 +112,7 @@ public class EntityRowMapper<T> implements RowMapper<T> { @@ -118,7 +112,7 @@ public class EntityRowMapper<T> implements RowMapper<T> {
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<T> implements RowMapper<T> { @@ -138,23 +132,19 @@ public class EntityRowMapper<T> implements RowMapper<T> {
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<S> 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> S createInstance(RelationalPersistentEntity<S> 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<T> implements RowMapper<T> { @@ -162,13 +152,13 @@ public class EntityRowMapper<T> implements RowMapper<T> {
@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> T getParameterValue(Parameter<T, RelationalPersistentProperty> parameter) {
@ -177,7 +167,7 @@ public class EntityRowMapper<T> implements RowMapper<T> { @@ -177,7 +167,7 @@ public class EntityRowMapper<T> implements RowMapper<T> {
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);
}

59
src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java

@ -0,0 +1,59 @@ @@ -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<Object> 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);
}
}

77
src/main/java/org/springframework/data/jdbc/core/mapping/JdbcSimpleTypes.java

@ -0,0 +1,77 @@ @@ -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<Class<?>> AUTOGENERATED_ID_TYPES;
static {
Set<Class<?>> 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<Class<?>> 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<Class<?>> JDBC_SIMPLE_TYPES;
public static final SimpleTypeHolder HOLDER = new SimpleTypeHolder(JDBC_SIMPLE_TYPES, true);
private JdbcSimpleTypes() {}
}

11
src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java

@ -29,6 +29,7 @@ import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -85,8 +88,8 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
DefaultDataAccessStrategy defaultDataAccessStrategy = new DefaultDataAccessStrategy( //
sqlGeneratorSource, //
context, //
converter, //
operations, //
instantiators, //
cascadingDataAccessStrategy //
);

36
src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java

@ -19,15 +19,20 @@ import java.util.Optional; @@ -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 { @@ -35,9 +40,30 @@ public class JdbcConfiguration {
@Bean
RelationalMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStrategy,
Optional<ConversionCustomizer> 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();
}
}

33
src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java

@ -17,13 +17,13 @@ package org.springframework.data.jdbc.repository.support; @@ -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; @@ -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 { @@ -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 { @@ -108,7 +111,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
? new EntityRowMapper<>( //
context.getRequiredPersistentEntity(domainType), //
context, //
instantiators, //
converter, //
accessStrategy) //
: typeMappedRowMapper;
}

30
src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java

@ -18,10 +18,10 @@ package org.springframework.data.jdbc.repository.support; @@ -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; @@ -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 { @@ -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 <T, ID> EntityInformation<T, ID> getEntityInformation(Class<T> aClass) {
@ -146,6 +138,6 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport { @@ -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));
}
}

21
src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java

@ -20,11 +20,11 @@ import java.io.Serializable; @@ -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,10 +47,10 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend @@ -47,10 +47,10 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, 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.
@ -80,7 +80,7 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend @@ -80,7 +80,7 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, 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<T extends Repository<S, ID>, S, ID extend @@ -115,9 +115,9 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, 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<T extends Repository<S, ID>, S, ID extend @@ -128,22 +128,19 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, 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();
}
}

256
src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java

@ -0,0 +1,256 @@ @@ -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.
* <p>
* 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<? extends RelationalPersistentEntity<?>, 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 RelationalPersistentEntity<?>, ? 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 RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
CustomConversions conversions) {
this(context, conversions, new DefaultConversionService(), new EntityInstantiators());
}
@SuppressWarnings("unchecked")
private BasicRelationalConverter(
MappingContext<? extends RelationalPersistentEntity<?>, ? 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 RelationalPersistentEntity<?>, ? 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 <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<T, ?> persistentEntity, T instance) {
PersistentPropertyAccessor<T> 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> T createInstance(PersistentEntity<T, RelationalPersistentProperty> entity,
ParameterValueProvider<RelationalPersistentProperty> 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<Class<?>> 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<Enum>) target, value.toString());
}
return conversionService.convert(value, target);
}
/**
* Converter-aware {@link ParameterValueProvider}.
*
* @param <P>
* @author Mark Paluch
*/
@RequiredArgsConstructor
class ConvertingParameterValueProvider<P extends PersistentProperty<P>> implements ParameterValueProvider<P> {
private final ParameterValueProvider<P> delegate;
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter)
*/
@Override
@SuppressWarnings("unchecked")
public <T> T getParameterValue(Parameter<T, P> parameter) {
return (T) readValue(delegate.getParameterValue(parameter), parameter.getType());
}
}
}

90
src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java

@ -0,0 +1,90 @@ @@ -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 RelationalPersistentEntity<?>, ? 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 <T>
* @return
*/
<T> T createInstance(PersistentEntity<T, RelationalPersistentProperty> entity,
ParameterValueProvider<RelationalPersistentProperty> parameterValueProvider);
/**
* Return a {@link PersistentPropertyAccessor} to access property values of the {@code instance}.
*
* @param persistentEntity
* @param instance
* @return
*/
<T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<T, ?> 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);
}

40
src/main/java/org/springframework/data/relational/core/mapping/ConversionCustomizer.java

@ -1,40 +0,0 @@ @@ -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);
}

51
src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java

@ -15,22 +15,12 @@ @@ -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; @@ -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<RelationalPersistentEntity<?>, RelationalPersistentProperty> {
private static final HashSet<Class<?>> 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<RelationalP @@ -147,8 +110,4 @@ public class RelationalMappingContext extends AbstractMappingContext<RelationalP
SimpleTypeHolder simpleTypeHolder) {
return new BasicRelationalPersistentProperty(property, owner, simpleTypeHolder, this);
}
public ConversionService getConversions() {
return conversions;
}
}

71
src/test/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategyUnitTests.java

@ -20,14 +20,21 @@ import static org.mockito.ArgumentMatchers.any; @@ -20,14 +20,21 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.HashMap;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
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.RelationalMappingContext;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
@ -37,6 +44,7 @@ import org.springframework.jdbc.support.KeyHolder; @@ -37,6 +44,7 @@ import org.springframework.jdbc.support.KeyHolder;
* Unit tests for {@link DefaultDataAccessStrategy}.
*
* @author Jens Schauder
* @author Mark Paluch
*/
public class DefaultDataAccessStrategyUnitTests {
@ -45,15 +53,15 @@ public class DefaultDataAccessStrategyUnitTests { @@ -45,15 +53,15 @@ public class DefaultDataAccessStrategyUnitTests {
NamedParameterJdbcOperations jdbcOperations = mock(NamedParameterJdbcOperations.class);
RelationalMappingContext context = new RelationalMappingContext();
RelationalConverter converter = new BasicRelationalConverter(context, new JdbcCustomConversions());
HashMap<String, Object> additionalParameters = new HashMap<>();
ArgumentCaptor<SqlParameterSource> 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 { @@ -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<String> 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<Boolean, String> {
INSTANCE;
@Override
public String convert(Boolean source) {
return source != null && source ? "T" : "F";
}
}
@ReadingConverter
enum StringToBooleanConverter implements Converter<String, Boolean> {
INSTANCE;
@Override
public Boolean convert(String source) {
return source != null && source.equalsIgnoreCase("T") ? Boolean.TRUE : Boolean.FALSE;
}
}
}

17
src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java

@ -38,21 +38,21 @@ import javax.naming.OperationNotSupportedException; @@ -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 { @@ -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<T>) context.getRequiredPersistentEntity(type), //
context, //
new EntityInstantiators(), //
converter, //
accessStrategy //
);
}

7
src/test/java/org/springframework/data/jdbc/mapping/model/NamingStrategyUnitTests.java

@ -16,7 +16,6 @@ @@ -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; @@ -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() {

8
src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java

@ -33,6 +33,7 @@ import org.springframework.context.annotation.Import; @@ -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; @@ -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 { @@ -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);
}
}

10
src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java

@ -36,11 +36,13 @@ import org.junit.Test; @@ -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 { @@ -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);
}

9
src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java

@ -25,12 +25,13 @@ import java.text.NumberFormat; @@ -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; @@ -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 { @@ -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);
}

4
src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java

@ -29,6 +29,7 @@ import org.springframework.data.annotation.Id; @@ -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; @@ -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 { @@ -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 { @@ -89,6 +92,7 @@ public class JdbcRepositoryFactoryBeanUnitTests {
public void afterPropertiesSetDefaultsNullablePropertiesCorrectly() {
factoryBean.setMappingContext(mappingContext);
factoryBean.setConverter(new BasicRelationalConverter(mappingContext));
factoryBean.setApplicationEventPublisher(publisher);
factoryBean.afterPropertiesSet();

42
src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java

@ -25,14 +25,16 @@ import org.springframework.context.ApplicationEventPublisher; @@ -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; @@ -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 { @@ -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 { @@ -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> namingStrategy, CustomConversions conversions) {
RelationalMappingContext mappingContext = new RelationalMappingContext(
namingStrategy.orElse(NamingStrategy.INSTANCE));
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
return mappingContext;
}
@Bean
RelationalMappingContext jdbcMappingContext(NamedParameterJdbcOperations template, Optional<NamingStrategy> namingStrategy,
Optional<ConversionCustomizer> 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);
}
}

104
src/test/java/org/springframework/data/relational/core/conversion/BasicRelationalConverterUnitTests.java

@ -0,0 +1,104 @@ @@ -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<MyEntity> entity = (RelationalPersistentEntity) context
.getRequiredPersistentEntity(MyEntity.class);
MyEntity instance = new MyEntity();
PersistentPropertyAccessor<MyEntity> 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<MyValue> entity = (RelationalPersistentEntity) context
.getRequiredPersistentEntity(MyValue.class);
MyValue result = converter.createInstance(entity, new ParameterValueProvider<RelationalPersistentProperty>() {
@Override
public <T> T getParameterValue(Parameter<T, RelationalPersistentProperty> 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;
}
}
Loading…
Cancel
Save