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.
This commit is contained in:
committed by
Jens Schauder
parent
9ad0cf0296
commit
fb858bf1b1
@@ -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();
|
||||
|
||||
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
|
||||
if (!property.isEntity()) {
|
||||
Object value = persistentEntity.getPropertyAccessor(instance).getProperty(property);
|
||||
PersistentPropertyAccessor<S> propertyAccessor = persistentEntity.getPropertyAccessor(instance);
|
||||
|
||||
Object convertedValue = convert(value, property.getColumnType());
|
||||
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
|
||||
|
||||
if (!property.isEntity()) {
|
||||
|
||||
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<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 {
|
||||
}
|
||||
|
||||
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 <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 {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<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> {
|
||||
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
|
||||
@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> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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> namingStrategy,
|
||||
Optional<ConversionCustomizer> conversionCustomizer) {
|
||||
CustomConversions customConversions) {
|
||||
|
||||
return new RelationalMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE),
|
||||
conversionCustomizer.orElse(ConversionCustomizer.NONE));
|
||||
RelationalMappingContext mappingContext = new RelationalMappingContext(
|
||||
namingStrategy.orElse(NamingStrategy.INSTANCE));
|
||||
mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
|
||||
|
||||
return mappingContext;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 <T, ID> EntityInformation<T, ID> getEntityInformation(Class<T> 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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.
|
||||
*
|
||||
*
|
||||
* @param repositoryInterface must not be {@literal null}.
|
||||
*/
|
||||
JdbcRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
|
||||
@@ -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
|
||||
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
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<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
|
||||
SimpleTypeHolder simpleTypeHolder) {
|
||||
return new BasicRelationalPersistentProperty(property, owner, simpleTypeHolder, this);
|
||||
}
|
||||
|
||||
public ConversionService getConversions() {
|
||||
return conversions;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user