Deprecate Basic…Converter infrastructure.

Merge basic converters into Mapping…Converters and introduce deprecated variant to provide guidance for migration off the deprecated types.

Cleanup no longer required code.

Original pull request #1618
See #1554
This commit is contained in:
Mark Paluch
2023-09-19 16:07:08 +02:00
committed by Jens Schauder
parent 4e3120ee0e
commit 5398b94020
35 changed files with 1155 additions and 1637 deletions

View File

@@ -15,7 +15,16 @@
*/
package org.springframework.data.jdbc.core;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
@@ -33,7 +42,6 @@ import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.conversion.DbActionExecutionResult;
import org.springframework.data.relational.core.conversion.IdValueSource;
import org.springframework.data.relational.core.mapping.AggregatePath;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@@ -188,7 +196,8 @@ class JdbcAggregateChangeExecutionContext {
private Object getParentId(DbAction.WithDependingOn<?> action) {
DbAction.WithEntity<?> idOwningAction = getIdOwningAction(action, context.getAggregatePath(action.getPropertyPath()).getIdDefiningParentPath());
DbAction.WithEntity<?> idOwningAction = getIdOwningAction(action,
context.getAggregatePath(action.getPropertyPath()).getIdDefiningParentPath());
return getPotentialGeneratedIdFrom(idOwningAction);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018-2023 the original author or authors.
* Copyright 2023 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.
@@ -15,78 +15,25 @@
*/
package org.springframework.data.jdbc.core.convert;
import java.sql.Array;
import java.sql.JDBCType;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLType;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.jdbc.core.mapping.JdbcValue;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.mapping.InstanceCreatorMetadata;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.mapping.model.SpELContext;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
import org.springframework.data.projection.EntityProjection;
import org.springframework.data.relational.core.conversion.MappingRelationalConverter;
import org.springframework.data.relational.core.conversion.ObjectPath;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.conversion.RowDocumentAccessor;
import org.springframework.data.relational.core.mapping.AggregatePath;
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.sql.IdentifierProcessing;
import org.springframework.data.relational.domain.RowDocument;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* {@link RelationalConverter} that uses a {@link MappingContext} to apply basic conversion of relational values to
* property values.
* {@link RelationalConverter} that uses a {@link MappingContext} to apply conversion of relational values to property
* values.
* <p>
* Conversion is configurable by providing a customized {@link CustomConversions}.
*
* @author Mark Paluch
* @author Jens Schauder
* @author Christoph Strobl
* @author Myeonghyeon Lee
* @author Chirag Tailor
* @see MappingContext
* @see SimpleTypeHolder
* @see CustomConversions
* @since 1.1
* @deprecated since 3.2, use {@link MappingJdbcConverter} instead as the naming suggests a limited scope of
* functionality.
*/
public class BasicJdbcConverter extends MappingRelationalConverter implements JdbcConverter, ApplicationContextAware {
private static final Log LOG = LogFactory.getLog(BasicJdbcConverter.class);
private static final Converter<Iterable<?>, Map<?, ?>> ITERABLE_OF_ENTRY_TO_MAP_CONVERTER = new IterableOfEntryToMapConverter();
private final JdbcTypeFactory typeFactory;
private final IdentifierProcessing identifierProcessing;
private final RelationResolver relationResolver;
private SpELContext spELContext;
@Deprecated(since = "3.2")
public class BasicJdbcConverter extends MappingJdbcConverter {
/**
* Creates a new {@link BasicJdbcConverter} given {@link MappingContext} and a {@link JdbcTypeFactory#unsupported()
@@ -98,15 +45,20 @@ public class BasicJdbcConverter extends MappingRelationalConverter implements Jd
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
*/
public BasicJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver) {
super(context, relationResolver);
}
super(context, new JdbcCustomConversions());
Assert.notNull(relationResolver, "RelationResolver must not be null");
this.typeFactory = JdbcTypeFactory.unsupported();
this.identifierProcessing = IdentifierProcessing.ANSI;
this.relationResolver = relationResolver;
this.spELContext = new SpELContext(ResultSetAccessorPropertyAccessor.INSTANCE);
/**
* Creates a new {@link BasicJdbcConverter} given {@link MappingContext}.
*
* @param context must not be {@literal null}.
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
* @param typeFactory must not be {@literal null}
* @since 3.2
*/
public BasicJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver,
CustomConversions conversions, JdbcTypeFactory typeFactory) {
super(context, relationResolver, conversions, typeFactory);
}
/**
@@ -120,714 +72,6 @@ public class BasicJdbcConverter extends MappingRelationalConverter implements Jd
*/
public BasicJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver,
CustomConversions conversions, JdbcTypeFactory typeFactory, IdentifierProcessing identifierProcessing) {
super(context, conversions);
Assert.notNull(typeFactory, "JdbcTypeFactory must not be null");
Assert.notNull(relationResolver, "RelationResolver must not be null");
Assert.notNull(identifierProcessing, "IdentifierProcessing must not be null");
this.typeFactory = typeFactory;
this.identifierProcessing = identifierProcessing;
this.relationResolver = relationResolver;
this.spELContext = new SpELContext(ResultSetAccessorPropertyAccessor.INSTANCE);
super(context, relationResolver, conversions, typeFactory, identifierProcessing);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.spELContext = new SpELContext(this.spELContext, applicationContext);
}
@Nullable
private Class<?> getEntityColumnType(Class<?> type) {
RelationalPersistentEntity<?> persistentEntity = getMappingContext().getPersistentEntity(type);
if (persistentEntity == null) {
return null;
}
RelationalPersistentProperty idProperty = persistentEntity.getIdProperty();
if (idProperty == null) {
return null;
}
return getColumnType(idProperty);
}
private Class<?> getReferenceColumnType(RelationalPersistentProperty property) {
Class<?> componentType = property.getTypeInformation().getRequiredComponentType().getType();
RelationalPersistentEntity<?> referencedEntity = getMappingContext().getRequiredPersistentEntity(componentType);
return getColumnType(referencedEntity.getRequiredIdProperty());
}
@Override
public SQLType getTargetSqlType(RelationalPersistentProperty property) {
return JdbcUtil.targetSqlTypeFor(getColumnType(property));
}
@Override
public Class<?> getColumnType(RelationalPersistentProperty property) {
return doGetColumnType(property);
}
private Class<?> doGetColumnType(RelationalPersistentProperty property) {
if (property.isAssociation()) {
return getReferenceColumnType(property);
}
if (property.isEntity()) {
Class<?> columnType = getEntityColumnType(property.getActualType());
if (columnType != null) {
return columnType;
}
}
Class<?> componentColumnType = JdbcColumnTypes.INSTANCE.resolvePrimitiveType(property.getActualType());
while (componentColumnType.isArray()) {
componentColumnType = componentColumnType.getComponentType();
}
if (property.isCollectionLike() && !property.isEntity()) {
return java.lang.reflect.Array.newInstance(componentColumnType, 0).getClass();
}
return componentColumnType;
}
@Override
@Nullable
public Object readValue(@Nullable Object value, TypeInformation<?> type) {
if (value == null) {
return value;
}
if (value instanceof Array) {
try {
return super.readValue(((Array) value).getArray(), type);
} catch (SQLException | ConverterNotFoundException e) {
LOG.info("Failed to extract a value of type %s from an Array; Attempting to use standard conversions", e);
}
}
return super.readValue(value, type);
}
@Override
@Nullable
public Object writeValue(@Nullable Object value, TypeInformation<?> type) {
if (value == null) {
return null;
}
return super.writeValue(value, type);
}
private boolean canWriteAsJdbcValue(@Nullable Object value) {
if (value == null) {
return true;
}
if (AggregateReference.class.isAssignableFrom(value.getClass())) {
return canWriteAsJdbcValue(((AggregateReference) value).getId());
}
RelationalPersistentEntity<?> persistentEntity = getMappingContext().getPersistentEntity(value.getClass());
if (persistentEntity != null) {
Object id = persistentEntity.getIdentifierAccessor(value).getIdentifier();
return canWriteAsJdbcValue(id);
}
if (value instanceof JdbcValue) {
return true;
}
Optional<Class<?>> customWriteTarget = getConversions().getCustomWriteTarget(value.getClass());
return customWriteTarget.isPresent() && customWriteTarget.get().isAssignableFrom(JdbcValue.class);
}
@Override
public JdbcValue writeJdbcValue(@Nullable Object value, Class<?> columnType, SQLType sqlType) {
JdbcValue jdbcValue = tryToConvertToJdbcValue(value);
if (jdbcValue != null) {
return jdbcValue;
}
Object convertedValue = writeValue(value, TypeInformation.of(columnType));
if (convertedValue == null || !convertedValue.getClass().isArray()) {
return JdbcValue.of(convertedValue, sqlType);
}
Class<?> componentType = convertedValue.getClass().getComponentType();
if (componentType != byte.class && componentType != Byte.class) {
Object[] objectArray = requireObjectArray(convertedValue);
return JdbcValue.of(typeFactory.createArray(objectArray), JDBCType.ARRAY);
}
if (componentType == Byte.class) {
convertedValue = ArrayUtils.toPrimitive((Byte[]) convertedValue);
}
return JdbcValue.of(convertedValue, JDBCType.BINARY);
}
@Nullable
private JdbcValue tryToConvertToJdbcValue(@Nullable Object value) {
if (canWriteAsJdbcValue(value)) {
Object converted = writeValue(value, TypeInformation.of(JdbcValue.class));
if (converted instanceof JdbcValue) {
return (JdbcValue) converted;
}
}
return null;
}
@Override
public <T> T mapRow(RelationalPersistentEntity<T> entity, ResultSet resultSet, Object key) {
return new ReadingContext<T>(getMappingContext().getAggregatePath(entity), new ResultSetAccessor(resultSet),
Identifier.empty(), key).mapRow();
}
@Override
public <T> T mapRow(AggregatePath path, ResultSet resultSet, Identifier identifier, Object key) {
return new ReadingContext<T>(path, new ResultSetAccessor(resultSet), identifier, key).mapRow();
}
@Override
public <R> R projectAndResolve(EntityProjection<R, ?> projection, RowDocument document) {
RelationalPersistentEntity<?> entity = getMappingContext()
.getRequiredPersistentEntity(projection.getActualDomainType());
ResolvingConversionContext context = new ResolvingConversionContext(newProjectingConversionContext(projection),
getMappingContext().getAggregatePath(entity), Identifier.empty());
return doReadProjection(context, document, projection);
}
@SuppressWarnings("unchecked")
@Override
public <R> R readAndResolve(Class<R> type, RowDocument source, Identifier identifier) {
RelationalPersistentEntity<R> entity = (RelationalPersistentEntity<R>) getMappingContext()
.getRequiredPersistentEntity(type);
AggregatePath path = getMappingContext().getAggregatePath(entity);
Identifier identifierToUse = ResolvingRelationalPropertyValueProvider.potentiallyAppendIdentifier(identifier,
entity, it -> source.get(it.getColumnName().getReference()));
ResolvingConversionContext context = new ResolvingConversionContext(getConversionContext(ObjectPath.ROOT), path,
identifierToUse);
return readAggregate(context, source, entity.getTypeInformation());
}
@Override
protected RelationalPropertyValueProvider newValueProvider(RowDocumentAccessor documentAccessor,
SpELExpressionEvaluator evaluator, ConversionContext context) {
if (context instanceof ResolvingConversionContext rcc) {
AggregatePathValueProvider delegate = (AggregatePathValueProvider) super.newValueProvider(documentAccessor,
evaluator, context);
return new ResolvingRelationalPropertyValueProvider(delegate, documentAccessor, rcc, rcc.identifier());
}
return super.newValueProvider(documentAccessor, evaluator, context);
}
/**
* {@link RelationalPropertyValueProvider} using a resolving context to lookup relations. This is highly
* context-sensitive. Note that the identifier is held here because of a chicken and egg problem, while
* {@link ResolvingConversionContext} hols the {@link AggregatePath}.
*/
class ResolvingRelationalPropertyValueProvider implements RelationalPropertyValueProvider {
private final AggregatePathValueProvider delegate;
private final RowDocumentAccessor accessor;
private final ResolvingConversionContext context;
private final Identifier identifier;
private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider delegate, RowDocumentAccessor accessor,
ResolvingConversionContext context, Identifier identifier) {
AggregatePath path = context.aggregatePath();
this.delegate = delegate;
this.accessor = accessor;
this.context = context;
this.identifier = path.isEntity()
? potentiallyAppendIdentifier(identifier, path.getRequiredLeafEntity(), delegate::getPropertyValue)
: identifier;
}
/**
* Conditionally append the identifier if the entity has an identifier property.
*/
static Identifier potentiallyAppendIdentifier(Identifier base, RelationalPersistentEntity<?> entity,
Function<RelationalPersistentProperty, Object> getter) {
if (entity.hasIdProperty()) {
RelationalPersistentProperty idProperty = entity.getRequiredIdProperty();
Object propertyValue = getter.apply(idProperty);
if (propertyValue != null) {
return base.withPart(idProperty.getColumnName(), propertyValue, idProperty.getType());
}
}
return base;
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public <T> T getPropertyValue(RelationalPersistentProperty property) {
AggregatePath aggregatePath = this.context.aggregatePath();
if (getConversions().isSimpleType(property.getActualType())) {
return (T) delegate.getValue(aggregatePath);
}
if (property.isEntity()) {
if (property.isCollectionLike() || property.isMap()) {
Identifier identifier1 = this.identifier;
if (property.getOwner().hasIdProperty()) {
Object id = this.identifier.get(property.getOwner().getRequiredIdProperty().getColumnName());
if (id != null) {
identifier1 = Identifier.of(aggregatePath.getTableInfo().reverseColumnInfo().name(), id, Object.class);
}
}
Iterable<Object> allByPath = relationResolver.findAllByPath(identifier1,
aggregatePath.getRequiredPersistentPropertyPath());
if (property.isCollectionLike()) {
return (T) allByPath;
}
if (property.isMap()) {
return (T) ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(allByPath);
}
Iterator<Object> iterator = allByPath.iterator();
if (iterator.hasNext()) {
return (T) iterator.next();
}
return null;
}
return hasValue(property) ? (T) readAggregate(this.context, accessor, property.getTypeInformation()) : null;
}
return (T) delegate.getValue(aggregatePath);
}
@Override
public boolean hasValue(RelationalPersistentProperty property) {
if (property.isCollectionLike() || property.isMap()) {
// attempt relation fetch
return true;
}
AggregatePath aggregatePath = context.aggregatePath();
if (property.isEntity()) {
RelationalPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(property);
if (entity.hasIdProperty()) {
RelationalPersistentProperty referenceId = entity.getRequiredIdProperty();
AggregatePath toUse = aggregatePath.append(referenceId);
return delegate.hasValue(toUse);
}
return delegate.hasValue(aggregatePath.getTableInfo().reverseColumnInfo().alias());
}
return delegate.hasValue(aggregatePath);
}
@Override
public RelationalPropertyValueProvider withContext(ConversionContext context) {
return context == this.context ? this
: new ResolvingRelationalPropertyValueProvider(delegate.withContext(context), accessor,
(ResolvingConversionContext) context, identifier);
}
}
/**
* Marker object to indicate that the property value provider should resolve relations.
*
* @param delegate
* @param aggregatePath
* @param identifier
*/
private record ResolvingConversionContext(ConversionContext delegate, AggregatePath aggregatePath,
Identifier identifier) implements ConversionContext {
@Override
public <S> S convert(Object source, TypeInformation<? extends S> typeHint) {
return delegate.convert(source, typeHint);
}
@Override
public <S> S convert(Object source, TypeInformation<? extends S> typeHint, ConversionContext context) {
return delegate.convert(source, typeHint, context);
}
@Override
public ResolvingConversionContext forProperty(String name) {
RelationalPersistentProperty property = aggregatePath.getRequiredLeafEntity().getRequiredPersistentProperty(name);
return forProperty(property);
}
@Override
public ResolvingConversionContext forProperty(RelationalPersistentProperty property) {
ConversionContext nested = delegate.forProperty(property);
return new ResolvingConversionContext(nested, aggregatePath.append(property), identifier);
}
@Override
public ResolvingConversionContext withPath(ObjectPath currentPath) {
return new ResolvingConversionContext(delegate.withPath(currentPath), aggregatePath, identifier);
}
@Override
public ObjectPath getPath() {
return delegate.getPath();
}
@Override
public CustomConversions getCustomConversions() {
return delegate.getCustomConversions();
}
@Override
public RelationalConverter getSourceConverter() {
return delegate.getSourceConverter();
}
}
static Object[] requireObjectArray(Object source) {
Assert.isTrue(source.getClass().isArray(), "Source object is not an array");
Class<?> componentType = source.getClass().getComponentType();
if (componentType.isPrimitive()) {
if (componentType == boolean.class) {
return ArrayUtils.toObject((boolean[]) source);
}
if (componentType == byte.class) {
return ArrayUtils.toObject((byte[]) source);
}
if (componentType == char.class) {
return ArrayUtils.toObject((char[]) source);
}
if (componentType == double.class) {
return ArrayUtils.toObject((double[]) source);
}
if (componentType == float.class) {
return ArrayUtils.toObject((float[]) source);
}
if (componentType == int.class) {
return ArrayUtils.toObject((int[]) source);
}
if (componentType == long.class) {
return ArrayUtils.toObject((long[]) source);
}
if (componentType == short.class) {
return ArrayUtils.toObject((short[]) source);
}
throw new IllegalArgumentException("Unsupported component type: " + componentType);
}
return (Object[]) source;
}
private class ReadingContext<T> {
private final RelationalPersistentEntity<T> entity;
private final AggregatePath rootPath;
private final AggregatePath path;
private final Identifier identifier;
private final Object key;
private final JdbcPropertyValueProvider propertyValueProvider;
private final JdbcBackReferencePropertyValueProvider backReferencePropertyValueProvider;
private final ResultSetAccessor accessor;
@SuppressWarnings("unchecked")
private ReadingContext(AggregatePath rootPath, ResultSetAccessor accessor, Identifier identifier, Object key) {
RelationalPersistentEntity<T> entity = (RelationalPersistentEntity<T>) rootPath.getLeafEntity();
Assert.notNull(entity, "The rootPath must point to an entity");
this.entity = entity;
this.rootPath = rootPath;
this.path = getMappingContext().getAggregatePath(this.entity);
this.identifier = identifier;
this.key = key;
this.propertyValueProvider = new JdbcPropertyValueProvider(path, accessor);
this.backReferencePropertyValueProvider = new JdbcBackReferencePropertyValueProvider(path, accessor);
this.accessor = accessor;
}
private ReadingContext(RelationalPersistentEntity<T> entity, AggregatePath rootPath, AggregatePath path,
Identifier identifier, Object key, JdbcPropertyValueProvider propertyValueProvider,
JdbcBackReferencePropertyValueProvider backReferencePropertyValueProvider, ResultSetAccessor accessor) {
this.entity = entity;
this.rootPath = rootPath;
this.path = path;
this.identifier = identifier;
this.key = key;
this.propertyValueProvider = propertyValueProvider;
this.backReferencePropertyValueProvider = backReferencePropertyValueProvider;
this.accessor = accessor;
}
private <S> ReadingContext<S> extendBy(RelationalPersistentProperty property) {
return new ReadingContext<>(
(RelationalPersistentEntity<S>) getMappingContext().getRequiredPersistentEntity(property.getActualType()),
rootPath.append(property), path.append(property), identifier, key, propertyValueProvider.extendBy(property),
backReferencePropertyValueProvider.extendBy(property), accessor);
}
T mapRow() {
RelationalPersistentProperty idProperty = entity.getIdProperty();
Object idValue = idProperty == null ? null : readFrom(idProperty);
return createInstanceInternal(idValue);
}
private T populateProperties(T instance, @Nullable Object idValue) {
PersistentPropertyAccessor<T> propertyAccessor = getPropertyAccessor(entity, instance);
InstanceCreatorMetadata<RelationalPersistentProperty> creatorMetadata = entity.getInstanceCreatorMetadata();
entity.doWithAll(property -> {
if (creatorMetadata != null && creatorMetadata.isCreatorParameter(property)) {
return;
}
// skip absent simple properties
if (isSimpleProperty(property)) {
if (!propertyValueProvider.hasProperty(property)) {
return;
}
}
Object value = readOrLoadProperty(idValue, property);
propertyAccessor.setProperty(property, value);
});
return propertyAccessor.getBean();
}
@Nullable
private Object readOrLoadProperty(@Nullable Object id, RelationalPersistentProperty property) {
if ((property.isCollectionLike() && property.isEntity()) || property.isMap()) {
Iterable<Object> allByPath = resolveRelation(id, property);
return property.isMap() //
? ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(allByPath) //
: allByPath;
} else if (property.isEmbedded()) {
return readEmbeddedEntityFrom(id, property);
} else {
return readFrom(property);
}
}
private Iterable<Object> resolveRelation(@Nullable Object id, RelationalPersistentProperty property) {
Identifier identifier = id == null //
? this.identifier.withPart(rootPath.getTableInfo().qualifierColumnInfo().name(), key, Object.class) //
: Identifier.of(rootPath.append(property).getTableInfo().reverseColumnInfo().name(), id, Object.class);
PersistentPropertyPath<? extends RelationalPersistentProperty> propertyPath = path.append(property)
.getRequiredPersistentPropertyPath();
return relationResolver.findAllByPath(identifier, propertyPath);
}
/**
* Read a single value or a complete Entity from the {@link ResultSet} passed as an argument.
*
* @param property the {@link RelationalPersistentProperty} for which the value is intended. Must not be
* {@code null}.
* @return the value read from the {@link ResultSet}. May be {@code null}.
*/
@Nullable
private Object readFrom(RelationalPersistentProperty property) {
if (property.isEntity()) {
return readEntityFrom(property);
}
Object value = propertyValueProvider.getPropertyValue(property);
return value != null ? readValue(value, property.getTypeInformation()) : null;
}
@Nullable
private Object readEmbeddedEntityFrom(@Nullable Object idValue, RelationalPersistentProperty property) {
ReadingContext<?> newContext = extendBy(property);
if (shouldCreateEmptyEmbeddedInstance(property) || newContext.hasInstanceValues(idValue)) {
return newContext.createInstanceInternal(idValue);
}
return null;
}
private boolean shouldCreateEmptyEmbeddedInstance(RelationalPersistentProperty property) {
return property.shouldCreateEmptyEmbedded();
}
private boolean hasInstanceValues(@Nullable Object idValue) {
RelationalPersistentEntity<?> persistentEntity = path.getRequiredLeafEntity();
for (RelationalPersistentProperty embeddedProperty : persistentEntity) {
// if the embedded contains Lists, Sets or Maps we consider it non-empty
if (embeddedProperty.isQualified() || embeddedProperty.isAssociation()) {
return true;
}
Object value = readOrLoadProperty(idValue, embeddedProperty);
if (value != null) {
return true;
}
}
return false;
}
@Nullable
@SuppressWarnings("unchecked")
private Object readEntityFrom(RelationalPersistentProperty property) {
ReadingContext<?> newContext = extendBy(property);
RelationalPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(property.getActualType());
RelationalPersistentProperty idProperty = entity.getIdProperty();
Object idValue;
if (idProperty != null) {
idValue = newContext.readFrom(idProperty);
} else {
idValue = backReferencePropertyValueProvider.getPropertyValue(property);
}
if (idValue == null) {
return null;
}
return newContext.createInstanceInternal(idValue);
}
private T createInstanceInternal(@Nullable Object idValue) {
InstanceCreatorMetadata<RelationalPersistentProperty> creatorMetadata = entity.getInstanceCreatorMetadata();
ParameterValueProvider<RelationalPersistentProperty> provider;
if (creatorMetadata != null && creatorMetadata.hasParameters()) {
SpELExpressionEvaluator expressionEvaluator = new DefaultSpELExpressionEvaluator(accessor, spELContext);
provider = new SpELExpressionParameterValueProvider<>(expressionEvaluator, getConversionService(),
new ResultSetParameterValueProvider(idValue, entity));
} else {
provider = NoOpParameterValueProvider.INSTANCE;
}
T instance = createInstance(entity, provider::getParameterValue);
return entity.requiresPropertyPopulation() ? populateProperties(instance, idValue) : instance;
}
/**
* {@link ParameterValueProvider} that reads a simple property or materializes an object for a
* {@link RelationalPersistentProperty}.
*
* @see #readOrLoadProperty(Object, RelationalPersistentProperty)
* @since 2.1
*/
private class ResultSetParameterValueProvider implements ParameterValueProvider<RelationalPersistentProperty> {
private final @Nullable Object idValue;
private final RelationalPersistentEntity<?> entity;
public ResultSetParameterValueProvider(@Nullable Object idValue, RelationalPersistentEntity<?> entity) {
this.idValue = idValue;
this.entity = entity;
}
@Override
@Nullable
public <T> T getParameterValue(Parameter<T, RelationalPersistentProperty> parameter) {
String parameterName = parameter.getName();
Assert.notNull(parameterName, "A constructor parameter name must not be null to be used with Spring Data JDBC");
RelationalPersistentProperty property = entity.getRequiredPersistentProperty(parameterName);
return (T) readOrLoadProperty(idValue, property);
}
}
}
private boolean isSimpleProperty(RelationalPersistentProperty property) {
return !property.isCollectionLike() && !property.isEntity() && !property.isMap() && !property.isEmbedded();
}
enum NoOpParameterValueProvider implements ParameterValueProvider<RelationalPersistentProperty> {
INSTANCE;
@Override
public <T> T getParameterValue(Parameter<T, RelationalPersistentProperty> parameter) {
return null;
}
}
}

View File

@@ -15,9 +15,7 @@
*/
package org.springframework.data.jdbc.core.convert;
import java.sql.Array;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import org.springframework.data.relational.core.mapping.AggregatePath;
@@ -25,7 +23,7 @@ import org.springframework.data.relational.core.mapping.PersistentPropertyPathEx
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.domain.RowDocument;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.lang.Nullable;
/**
* Maps a {@link ResultSet} to an entity of type {@code T}, including entities referenced. This {@link RowMapper} might
@@ -43,7 +41,7 @@ public class EntityRowMapper<T> implements RowMapper<T> {
private final RelationalPersistentEntity<T> entity;
private final AggregatePath path;
private final JdbcConverter converter;
private final Identifier identifier;
private final @Nullable Identifier identifier;
/**
* @deprecated use {@link EntityRowMapper#EntityRowMapper(AggregatePath, JdbcConverter, Identifier)} instead
@@ -78,39 +76,11 @@ public class EntityRowMapper<T> implements RowMapper<T> {
@Override
public T mapRow(ResultSet resultSet, int rowNumber) throws SQLException {
RowDocument document = toRowDocument(resultSet);
RowDocument document = RowDocumentResultSetExtractor.toRowDocument(resultSet);
// TODO: Remove mapRow methods.
if (true) {
return path == null //
? converter.readAndResolve(entity.getType(), document) //
: converter.readAndResolve(entity.getType(), document, identifier);
}
return path == null //
? converter.mapRow(entity, resultSet, rowNumber) //
: converter.mapRow(path, resultSet, identifier, rowNumber);
return identifier == null //
? converter.readAndResolve(entity.getType(), document) //
: converter.readAndResolve(entity.getType(), document, identifier);
}
/**
* Create a {@link RowDocument} from the current {@link ResultSet} row.
*
* @param resultSet must not be {@literal null}.
* @return
* @throws SQLException
*/
static RowDocument toRowDocument(ResultSet resultSet) throws SQLException {
ResultSetMetaData md = resultSet.getMetaData();
int columnCount = md.getColumnCount();
RowDocument document = new RowDocument(columnCount);
for (int i = 0; i < columnCount; i++) {
Object rsv = JdbcUtils.getResultSetValue(resultSet, i + 1);
String columnName = md.getColumnLabel(i + 1);
document.put(columnName, rsv instanceof Array a ? a.getArray() : rsv);
}
return document;
}
}

View File

@@ -16,12 +16,11 @@
package org.springframework.data.jdbc.core.convert;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLType;
import org.springframework.data.jdbc.core.mapping.JdbcValue;
import org.springframework.data.projection.EntityProjection;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.mapping.AggregatePath;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
@@ -58,8 +57,16 @@ public interface JdbcConverter extends RelationalConverter {
* @param key primary key.
* @param <T>
* @return
* @deprecated since 3.2, use {@link #readAndResolve(Class, RowDocument, Identifier)} instead.
*/
<T> T mapRow(RelationalPersistentEntity<T> entity, ResultSet resultSet, Object key);
@Deprecated(since = "3.2")
default <T> T mapRow(RelationalPersistentEntity<T> entity, ResultSet resultSet, Object key) {
try {
return readAndResolve(entity.getType(), RowDocumentResultSetExtractor.toRowDocument(resultSet));
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* Read the current row from {@link ResultSet} to an {@link PersistentPropertyPathExtension#getActualType() entity}.
@@ -70,39 +77,19 @@ public interface JdbcConverter extends RelationalConverter {
* @param key primary key.
* @param <T>
* @return
* @deprecated use {@link #mapRow(AggregatePath, ResultSet, Identifier, Object)} instead.
* @deprecated use {@link #readAndResolve(Class, RowDocument, Identifier)} instead.
*/
@SuppressWarnings("unchecked")
@Deprecated(since = "3.2", forRemoval = true)
default <T> T mapRow(PersistentPropertyPathExtension path, ResultSet resultSet, Identifier identifier, Object key) {
return mapRow(path.getAggregatePath(), resultSet, identifier, key);
try {
return (T) readAndResolve(path.getRequiredLeafEntity().getType(),
RowDocumentResultSetExtractor.toRowDocument(resultSet), identifier);
} catch (SQLException e) {
throw new RuntimeException(e);
}
};
/**
* Read the current row from {@link ResultSet} to an {@link AggregatePath#getLeafEntity()} entity}.
*
* @param path path to the owning property.
* @param resultSet the {@link ResultSet} to read from.
* @param identifier entity identifier.
* @param key primary key.
* @param <T>
* @return
*/
<T> T mapRow(AggregatePath path, ResultSet resultSet, Identifier identifier, Object key);
/**
* Apply a projection to {@link RowDocument} and return the projection return type {@code R}.
* {@link EntityProjection#isProjection() Non-projecting} descriptors fall back to {@link #read(Class, RowDocument)
* regular object materialization}.
*
* @param descriptor the projection descriptor, must not be {@literal null}.
* @param document must not be {@literal null}.
* @param <R>
* @return a new instance of the projection return type {@code R}.
* @since 3.2
* @see #project(EntityProjection, RowDocument)
*/
<R> R projectAndResolve(EntityProjection<R, ?> descriptor, RowDocument document);
/**
* Read a {@link RowDocument} into the requested {@link Class aggregate type} and resolve references by looking these
* up from {@link RelationResolver}.

View File

@@ -55,14 +55,11 @@ class MapEntityRowMapper<T> implements RowMapper<Map.Entry<Object, T>> {
return new HashMap.SimpleEntry<>(key, mapEntity(rs, key));
}
@SuppressWarnings("unchecked")
private T mapEntity(ResultSet resultSet, Object key) throws SQLException {
if (true) {
RowDocument document = EntityRowMapper.toRowDocument(resultSet);
return (T) converter.readAndResolve(path.getLeafEntity().getType(), document,
identifier.withPart(keyColumn, key, Object.class));
}
return converter.mapRow(path, resultSet, identifier, key);
RowDocument document = RowDocumentResultSetExtractor.toRowDocument(resultSet);
return (T) converter.readAndResolve(path.getLeafEntity().getType(), document,
identifier.withPart(keyColumn, key, Object.class));
}
}

View File

@@ -0,0 +1,537 @@
/*
* Copyright 2023 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
*
* https://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.sql.Array;
import java.sql.JDBCType;
import java.sql.SQLException;
import java.sql.SQLType;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.jdbc.core.mapping.JdbcValue;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.relational.core.conversion.MappingRelationalConverter;
import org.springframework.data.relational.core.conversion.ObjectPath;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.conversion.RowDocumentAccessor;
import org.springframework.data.relational.core.mapping.AggregatePath;
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.sql.IdentifierProcessing;
import org.springframework.data.relational.domain.RowDocument;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* {@link RelationalConverter} that uses a {@link MappingContext} to apply conversion of relational values to property
* values.
* <p>
* Conversion is configurable by providing a customized {@link CustomConversions}.
*
* @author Mark Paluch
* @author Jens Schauder
* @author Christoph Strobl
* @author Myeonghyeon Lee
* @author Chirag Tailor
* @see MappingContext
* @see SimpleTypeHolder
* @see CustomConversions
* @since 3.2
*/
public class MappingJdbcConverter extends MappingRelationalConverter implements JdbcConverter, ApplicationContextAware {
private static final Log LOG = LogFactory.getLog(MappingJdbcConverter.class);
private static final Converter<Iterable<?>, Map<?, ?>> ITERABLE_OF_ENTRY_TO_MAP_CONVERTER = new IterableOfEntryToMapConverter();
private final JdbcTypeFactory typeFactory;
private final RelationResolver relationResolver;
/**
* Creates a new {@link MappingJdbcConverter} given {@link MappingContext} and a {@link JdbcTypeFactory#unsupported()
* no-op type factory} throwing {@link UnsupportedOperationException} on type creation. Use
* {@link #MappingJdbcConverter(RelationalMappingContext, RelationResolver, CustomConversions, JdbcTypeFactory, IdentifierProcessing)}
* (MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and large objects into JDBC-specific types.
*
* @param context must not be {@literal null}.
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
*/
public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver) {
super(context, new JdbcCustomConversions());
Assert.notNull(relationResolver, "RelationResolver must not be null");
this.typeFactory = JdbcTypeFactory.unsupported();
this.relationResolver = relationResolver;
}
/**
* Creates a new {@link MappingJdbcConverter} given {@link MappingContext}.
*
* @param context must not be {@literal null}.
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
* @param typeFactory must not be {@literal null}
*/
public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver,
CustomConversions conversions, JdbcTypeFactory typeFactory) {
super(context, conversions);
Assert.notNull(typeFactory, "JdbcTypeFactory must not be null");
Assert.notNull(relationResolver, "RelationResolver must not be null");
this.typeFactory = typeFactory;
this.relationResolver = relationResolver;
}
MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver,
CustomConversions conversions, JdbcTypeFactory typeFactory, IdentifierProcessing identifierProcessing) {
super(context, conversions);
this.relationResolver = relationResolver;
this.typeFactory = typeFactory;
}
@Nullable
private Class<?> getEntityColumnType(Class<?> type) {
RelationalPersistentEntity<?> persistentEntity = getMappingContext().getPersistentEntity(type);
if (persistentEntity == null) {
return null;
}
RelationalPersistentProperty idProperty = persistentEntity.getIdProperty();
if (idProperty == null) {
return null;
}
return getColumnType(idProperty);
}
private Class<?> getReferenceColumnType(RelationalPersistentProperty property) {
Class<?> componentType = property.getTypeInformation().getRequiredComponentType().getType();
RelationalPersistentEntity<?> referencedEntity = getMappingContext().getRequiredPersistentEntity(componentType);
return getColumnType(referencedEntity.getRequiredIdProperty());
}
@Override
public SQLType getTargetSqlType(RelationalPersistentProperty property) {
return JdbcUtil.targetSqlTypeFor(getColumnType(property));
}
@Override
public Class<?> getColumnType(RelationalPersistentProperty property) {
return doGetColumnType(property);
}
private Class<?> doGetColumnType(RelationalPersistentProperty property) {
if (property.isAssociation()) {
return getReferenceColumnType(property);
}
if (property.isEntity()) {
Class<?> columnType = getEntityColumnType(property.getActualType());
if (columnType != null) {
return columnType;
}
}
Class<?> componentColumnType = JdbcColumnTypes.INSTANCE.resolvePrimitiveType(property.getActualType());
while (componentColumnType.isArray()) {
componentColumnType = componentColumnType.getComponentType();
}
if (property.isCollectionLike() && !property.isEntity()) {
return java.lang.reflect.Array.newInstance(componentColumnType, 0).getClass();
}
return componentColumnType;
}
@Override
@Nullable
public Object readValue(@Nullable Object value, TypeInformation<?> type) {
if (value == null) {
return value;
}
if (value instanceof Array) {
try {
return super.readValue(((Array) value).getArray(), type);
} catch (SQLException | ConverterNotFoundException e) {
LOG.info("Failed to extract a value of type %s from an Array; Attempting to use standard conversions", e);
}
}
return super.readValue(value, type);
}
@Override
@Nullable
public Object writeValue(@Nullable Object value, TypeInformation<?> type) {
if (value == null) {
return null;
}
return super.writeValue(value, type);
}
private boolean canWriteAsJdbcValue(@Nullable Object value) {
if (value == null) {
return true;
}
if (AggregateReference.class.isAssignableFrom(value.getClass())) {
return canWriteAsJdbcValue(((AggregateReference) value).getId());
}
RelationalPersistentEntity<?> persistentEntity = getMappingContext().getPersistentEntity(value.getClass());
if (persistentEntity != null) {
Object id = persistentEntity.getIdentifierAccessor(value).getIdentifier();
return canWriteAsJdbcValue(id);
}
if (value instanceof JdbcValue) {
return true;
}
Optional<Class<?>> customWriteTarget = getConversions().getCustomWriteTarget(value.getClass());
return customWriteTarget.isPresent() && customWriteTarget.get().isAssignableFrom(JdbcValue.class);
}
@Override
public JdbcValue writeJdbcValue(@Nullable Object value, Class<?> columnType, SQLType sqlType) {
JdbcValue jdbcValue = tryToConvertToJdbcValue(value);
if (jdbcValue != null) {
return jdbcValue;
}
Object convertedValue = writeValue(value, TypeInformation.of(columnType));
if (convertedValue == null || !convertedValue.getClass().isArray()) {
return JdbcValue.of(convertedValue, sqlType);
}
Class<?> componentType = convertedValue.getClass().getComponentType();
if (componentType != byte.class && componentType != Byte.class) {
Object[] objectArray = requireObjectArray(convertedValue);
return JdbcValue.of(typeFactory.createArray(objectArray), JDBCType.ARRAY);
}
if (componentType == Byte.class) {
convertedValue = ArrayUtils.toPrimitive((Byte[]) convertedValue);
}
return JdbcValue.of(convertedValue, JDBCType.BINARY);
}
@Nullable
private JdbcValue tryToConvertToJdbcValue(@Nullable Object value) {
if (canWriteAsJdbcValue(value)) {
Object converted = writeValue(value, TypeInformation.of(JdbcValue.class));
if (converted instanceof JdbcValue) {
return (JdbcValue) converted;
}
}
return null;
}
@SuppressWarnings("unchecked")
@Override
public <R> R readAndResolve(Class<R> type, RowDocument source, Identifier identifier) {
RelationalPersistentEntity<R> entity = (RelationalPersistentEntity<R>) getMappingContext()
.getRequiredPersistentEntity(type);
AggregatePath path = getMappingContext().getAggregatePath(entity);
Identifier identifierToUse = ResolvingRelationalPropertyValueProvider.potentiallyAppendIdentifier(identifier,
entity, it -> source.get(it.getColumnName().getReference()));
ResolvingConversionContext context = new ResolvingConversionContext(getConversionContext(ObjectPath.ROOT), path,
identifierToUse);
return readAggregate(context, source, entity.getTypeInformation());
}
@Override
protected RelationalPropertyValueProvider newValueProvider(RowDocumentAccessor documentAccessor,
SpELExpressionEvaluator evaluator, ConversionContext context) {
if (context instanceof ResolvingConversionContext rcc) {
AggregatePathValueProvider delegate = (AggregatePathValueProvider) super.newValueProvider(documentAccessor,
evaluator, context);
return new ResolvingRelationalPropertyValueProvider(delegate, documentAccessor, rcc, rcc.identifier());
}
return super.newValueProvider(documentAccessor, evaluator, context);
}
/**
* {@link RelationalPropertyValueProvider} using a resolving context to lookup relations. This is highly
* context-sensitive. Note that the identifier is held here because of a chicken and egg problem, while
* {@link ResolvingConversionContext} hols the {@link AggregatePath}.
*/
class ResolvingRelationalPropertyValueProvider implements RelationalPropertyValueProvider {
private final AggregatePathValueProvider delegate;
private final RowDocumentAccessor accessor;
private final ResolvingConversionContext context;
private final Identifier identifier;
private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider delegate, RowDocumentAccessor accessor,
ResolvingConversionContext context, Identifier identifier) {
AggregatePath path = context.aggregatePath();
this.delegate = delegate;
this.accessor = accessor;
this.context = context;
this.identifier = path.isEntity()
? potentiallyAppendIdentifier(identifier, path.getRequiredLeafEntity(), delegate::getPropertyValue)
: identifier;
}
/**
* Conditionally append the identifier if the entity has an identifier property.
*/
static Identifier potentiallyAppendIdentifier(Identifier base, RelationalPersistentEntity<?> entity,
Function<RelationalPersistentProperty, Object> getter) {
if (entity.hasIdProperty()) {
RelationalPersistentProperty idProperty = entity.getRequiredIdProperty();
Object propertyValue = getter.apply(idProperty);
if (propertyValue != null) {
return base.withPart(idProperty.getColumnName(), propertyValue, idProperty.getType());
}
}
return base;
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public <T> T getPropertyValue(RelationalPersistentProperty property) {
AggregatePath aggregatePath = this.context.aggregatePath();
if (getConversions().isSimpleType(property.getActualType())) {
return (T) delegate.getValue(aggregatePath);
}
if (property.isEntity()) {
if (property.isCollectionLike() || property.isMap()) {
Identifier identifierToUse = this.identifier;
if (property.getOwner().hasIdProperty()) {
Object id = this.identifier.get(property.getOwner().getRequiredIdProperty().getColumnName());
if (id != null) {
identifierToUse = Identifier.of(aggregatePath.getTableInfo().reverseColumnInfo().name(), id,
Object.class);
}
}
Iterable<Object> allByPath = relationResolver.findAllByPath(identifierToUse,
aggregatePath.getRequiredPersistentPropertyPath());
if (property.isCollectionLike()) {
return (T) allByPath;
}
if (property.isMap()) {
return (T) ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(allByPath);
}
Iterator<Object> iterator = allByPath.iterator();
if (iterator.hasNext()) {
return (T) iterator.next();
}
return null;
}
return hasValue(property) ? (T) readAggregate(this.context, accessor, property.getTypeInformation()) : null;
}
return (T) delegate.getValue(aggregatePath);
}
@Override
public boolean hasValue(RelationalPersistentProperty property) {
if (property.isCollectionLike() || property.isMap()) {
// attempt relation fetch
return true;
}
AggregatePath aggregatePath = context.aggregatePath();
if (property.isEntity()) {
RelationalPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(property);
if (entity.hasIdProperty()) {
RelationalPersistentProperty referenceId = entity.getRequiredIdProperty();
AggregatePath toUse = aggregatePath.append(referenceId);
return delegate.hasValue(toUse);
}
return delegate.hasValue(aggregatePath.getTableInfo().reverseColumnInfo().alias());
}
return delegate.hasValue(aggregatePath);
}
@Override
public RelationalPropertyValueProvider withContext(ConversionContext context) {
return context == this.context ? this
: new ResolvingRelationalPropertyValueProvider(delegate.withContext(context), accessor,
(ResolvingConversionContext) context, identifier);
}
}
/**
* Marker object to indicate that the property value provider should resolve relations.
*
* @param delegate
* @param aggregatePath
* @param identifier
*/
private record ResolvingConversionContext(ConversionContext delegate, AggregatePath aggregatePath,
Identifier identifier) implements ConversionContext {
@Override
public <S> S convert(Object source, TypeInformation<? extends S> typeHint) {
return delegate.convert(source, typeHint);
}
@Override
public <S> S convert(Object source, TypeInformation<? extends S> typeHint, ConversionContext context) {
return delegate.convert(source, typeHint, context);
}
@Override
public ResolvingConversionContext forProperty(String name) {
RelationalPersistentProperty property = aggregatePath.getRequiredLeafEntity().getRequiredPersistentProperty(name);
return forProperty(property);
}
@Override
public ResolvingConversionContext forProperty(RelationalPersistentProperty property) {
ConversionContext nested = delegate.forProperty(property);
return new ResolvingConversionContext(nested, aggregatePath.append(property), identifier);
}
@Override
public ResolvingConversionContext withPath(ObjectPath currentPath) {
return new ResolvingConversionContext(delegate.withPath(currentPath), aggregatePath, identifier);
}
@Override
public ObjectPath getPath() {
return delegate.getPath();
}
@Override
public CustomConversions getCustomConversions() {
return delegate.getCustomConversions();
}
@Override
public RelationalConverter getSourceConverter() {
return delegate.getSourceConverter();
}
}
static Object[] requireObjectArray(Object source) {
Assert.isTrue(source.getClass().isArray(), "Source object is not an array");
Class<?> componentType = source.getClass().getComponentType();
if (componentType.isPrimitive()) {
if (componentType == boolean.class) {
return ArrayUtils.toObject((boolean[]) source);
}
if (componentType == byte.class) {
return ArrayUtils.toObject((byte[]) source);
}
if (componentType == char.class) {
return ArrayUtils.toObject((char[]) source);
}
if (componentType == double.class) {
return ArrayUtils.toObject((double[]) source);
}
if (componentType == float.class) {
return ArrayUtils.toObject((float[]) source);
}
if (componentType == int.class) {
return ArrayUtils.toObject((int[]) source);
}
if (componentType == long.class) {
return ArrayUtils.toObject((long[]) source);
}
if (componentType == short.class) {
return ArrayUtils.toObject((short[]) source);
}
throw new IllegalArgumentException("Unsupported component type: " + componentType);
}
return (Object[]) source;
}
}

View File

@@ -52,6 +52,28 @@ class RowDocumentResultSetExtractor {
this.propertyToColumn = propertyToColumn;
}
/**
* Create a {@link RowDocument} from the current {@link ResultSet} row.
*
* @param resultSet must not be {@literal null}.
* @return
* @throws SQLException
*/
static RowDocument toRowDocument(ResultSet resultSet) throws SQLException {
ResultSetMetaData md = resultSet.getMetaData();
int columnCount = md.getColumnCount();
RowDocument document = new RowDocument(columnCount);
for (int i = 0; i < columnCount; i++) {
Object rsv = JdbcUtils.getResultSetValue(resultSet, i + 1);
String columnName = md.getColumnLabel(i + 1);
document.put(columnName, rsv instanceof Array a ? a.getArray() : rsv);
}
return document;
}
/**
* Adapter to extract values and column metadata from a {@link ResultSet}.
*/

View File

@@ -135,8 +135,7 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware {
: JdbcArrayColumns.DefaultSupport.INSTANCE;
DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations(), arrayColumns);
return new BasicJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory,
dialect.getIdentifierProcessing());
return new MappingJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory);
}
/**

View File

@@ -31,15 +31,15 @@ import org.junit.jupiter.api.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.relational.core.conversion.RootAggregateChange;
import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.conversion.IdValueSource;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
import org.springframework.data.relational.core.conversion.RootAggregateChange;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.lang.Nullable;
@@ -61,7 +61,7 @@ public class AggregateChangeIdGenerationUnitTests {
Tag tag3 = new Tag();
RelationalMappingContext context = new RelationalMappingContext();
JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
JdbcConverter converter = new MappingJdbcConverter(context, (identifier, path) -> {
throw new UnsupportedOperationException();
});
DbAction.WithRoot<DummyEntity> rootInsert = new DbAction.InsertRoot<>(entity, IdValueSource.GENERATED);

View File

@@ -15,14 +15,21 @@
*/
package org.springframework.data.jdbc.core;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.List;
import java.util.Objects;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.Identifier;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder;
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.relational.core.conversion.DbAction;
@@ -32,12 +39,6 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.lang.Nullable;
import java.util.List;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* Test for the {@link JdbcAggregateChangeExecutionContext} when operating on immutable classes.
*
@@ -47,7 +48,7 @@ import static org.mockito.Mockito.*;
public class JdbcAggregateChangeExecutorContextImmutableUnitTests {
RelationalMappingContext context = new RelationalMappingContext();
JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
JdbcConverter converter = new MappingJdbcConverter(context, (identifier, path) -> {
throw new UnsupportedOperationException();
});
DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class);
@@ -221,19 +222,20 @@ public class JdbcAggregateChangeExecutorContextImmutableUnitTests {
public boolean equals(final Object o) {
if (o == this) return true;
if (!(o instanceof DummyEntity)) return false;
final DummyEntity other = (DummyEntity) o;
if (!(o instanceof final DummyEntity other))
return false;
final Object this$id = this.getId();
final Object other$id = other.getId();
if (this$id == null ? other$id != null : !this$id.equals(other$id)) return false;
if (!Objects.equals(this$id, other$id))
return false;
if (this.getVersion() != other.getVersion()) return false;
final Object this$content = this.getContent();
final Object other$content = other.getContent();
if (this$content == null ? other$content != null : !this$content.equals(other$content)) return false;
if (!Objects.equals(this$content, other$content))
return false;
final Object this$list = this.getList();
final Object other$list = other.getList();
if (this$list == null ? other$list != null : !this$list.equals(other$list)) return false;
return true;
return Objects.equals(this$list, other$list);
}
public int hashCode() {
@@ -289,12 +291,11 @@ public class JdbcAggregateChangeExecutorContextImmutableUnitTests {
public boolean equals(final Object o) {
if (o == this) return true;
if (!(o instanceof Content)) return false;
final Content other = (Content) o;
if (!(o instanceof final Content other))
return false;
final Object this$id = this.getId();
final Object other$id = other.getId();
if (this$id == null ? other$id != null : !this$id.equals(other$id)) return false;
return true;
return Objects.equals(this$id, other$id);
}
public int hashCode() {

View File

@@ -25,11 +25,11 @@ import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.Identifier;
import org.springframework.data.jdbc.core.convert.InsertSubject;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.relational.core.conversion.DbAction;
@@ -50,7 +50,7 @@ import org.springframework.lang.Nullable;
public class JdbcAggregateChangeExecutorContextUnitTests {
RelationalMappingContext context = new RelationalMappingContext();
JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
JdbcConverter converter = new MappingJdbcConverter(context, (identifier, path) -> {
throw new UnsupportedOperationException();
});
DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class);

View File

@@ -15,6 +15,11 @@
*/
package org.springframework.data.jdbc.core;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -26,9 +31,9 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
import org.springframework.data.jdbc.core.convert.RelationResolver;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
@@ -41,11 +46,6 @@ import org.springframework.data.relational.core.mapping.event.BeforeConvertCallb
import org.springframework.data.relational.core.mapping.event.BeforeDeleteCallback;
import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* Unit tests for {@link JdbcAggregateTemplate}.
*
@@ -72,10 +72,10 @@ public class JdbcAggregateTemplateUnitTests {
public void setUp() {
RelationalMappingContext mappingContext = new RelationalMappingContext();
JdbcConverter converter = new BasicJdbcConverter(mappingContext, relationResolver);
JdbcConverter converter = new MappingJdbcConverter(mappingContext, relationResolver);
template = new JdbcAggregateTemplate(eventPublisher, mappingContext, converter, dataAccessStrategy);
((JdbcAggregateTemplate) template).setEntityCallbacks(callbacks);
template.setEntityCallbacks(callbacks);
}

View File

@@ -28,15 +28,14 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProp
import org.springframework.data.util.TypeInformation;
/**
* Unit tests for the handling of {@link AggregateReference}s in the
* {@link org.springframework.data.jdbc.core.convert.BasicJdbcConverter}.
* Unit tests for the handling of {@link AggregateReference}s in the {@link MappingJdbcConverter}.
*
* @author Jens Schauder
*/
public class BasicRelationalConverterAggregateReferenceUnitTests {
JdbcMappingContext context = new JdbcMappingContext();
JdbcConverter converter = new BasicJdbcConverter(context, mock(RelationResolver.class));
JdbcConverter converter = new MappingJdbcConverter(context, mock(RelationResolver.class));
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(DummyEntity.class);

View File

@@ -45,11 +45,11 @@ class DefaultDataAccessStrategyUnitTests {
static final long ORIGINAL_ID = 4711L;
private NamedParameterJdbcOperations namedJdbcOperations = mock(NamedParameterJdbcOperations.class);
private JdbcOperations jdbcOperations = mock(JdbcOperations.class);
private RelationalMappingContext context = new JdbcMappingContext();
private SqlParametersFactory sqlParametersFactory = mock(SqlParametersFactory.class);
private InsertStrategyFactory insertStrategyFactory = mock(InsertStrategyFactory.class);
private final NamedParameterJdbcOperations namedJdbcOperations = mock(NamedParameterJdbcOperations.class);
private final JdbcOperations jdbcOperations = mock(JdbcOperations.class);
private final RelationalMappingContext context = new JdbcMappingContext();
private final SqlParametersFactory sqlParametersFactory = mock(SqlParametersFactory.class);
private final InsertStrategyFactory insertStrategyFactory = mock(InsertStrategyFactory.class);
private JdbcConverter converter;
private DataAccessStrategy accessStrategy;
@@ -59,7 +59,7 @@ class DefaultDataAccessStrategyUnitTests {
DelegatingDataAccessStrategy relationResolver = new DelegatingDataAccessStrategy();
Dialect dialect = HsqlDbDialect.INSTANCE;
converter = new BasicJdbcConverter(context, relationResolver, new JdbcCustomConversions(),
converter = new MappingJdbcConverter(context, relationResolver, new JdbcCustomConversions(),
new DefaultJdbcTypeFactory(jdbcOperations), dialect.getIdentifierProcessing());
accessStrategy = new DataAccessStrategyFactory( //
new SqlGeneratorSource(context, converter, dialect), //

View File

@@ -15,6 +15,28 @@
*/
package org.springframework.data.jdbc.core.convert;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.SoftAssertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.naming.OperationNotSupportedException;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.invocation.InvocationOnMock;
@@ -37,26 +59,6 @@ import org.springframework.data.repository.query.Param;
import org.springframework.util.Assert;
import org.springframework.util.LinkedCaseInsensitiveMap;
import javax.naming.OperationNotSupportedException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.SoftAssertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
/**
* Tests the extraction of entities from a {@link ResultSet} by the {@link EntityRowMapper}.
*
@@ -490,7 +492,7 @@ public class EntityRowMapperUnitTests {
@Test // DATAJDBC-341
void immutableEmbeddedWithAllColumnsMissingShouldBeNull() throws SQLException {
ResultSet rs = mockResultSet(asList("ID"), //
ResultSet rs = mockResultSet(List.of("ID"), //
ID_FOR_ENTITY_NOT_REFERENCING_MAP);
rs.next();
@@ -564,7 +566,7 @@ public class EntityRowMapperUnitTests {
@Test // DATAJDBC-341
void primitiveEmbeddedShouldBeNullWhenAllColumnsAreMissing() throws SQLException {
ResultSet rs = mockResultSet(asList("ID"), //
ResultSet rs = mockResultSet(List.of("ID"), //
ID_FOR_ENTITY_NOT_REFERENCING_MAP);
rs.next();
@@ -677,16 +679,17 @@ public class EntityRowMapperUnitTests {
public boolean equals(final Object o) {
if (o == this) return true;
if (!(o instanceof Trivial)) return false;
final Trivial other = (Trivial) o;
if (!other.canEqual((Object) this)) return false;
if (!(o instanceof final Trivial other))
return false;
if (!other.canEqual(this))
return false;
final Object this$id = this.getId();
final Object other$id = other.getId();
if (this$id == null ? other$id != null : !this$id.equals(other$id)) return false;
if (!Objects.equals(this$id, other$id))
return false;
final Object this$name = this.getName();
final Object other$name = other.getName();
if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
return true;
return Objects.equals(this$name, other$name);
}
protected boolean canEqual(final Object other) {
@@ -746,22 +749,24 @@ public class EntityRowMapperUnitTests {
public boolean equals(final Object o) {
if (o == this) return true;
if (!(o instanceof TrivialMapPropertiesToNullIfNotNeeded)) return false;
final TrivialMapPropertiesToNullIfNotNeeded other = (TrivialMapPropertiesToNullIfNotNeeded) o;
if (!other.canEqual((Object) this)) return false;
if (!(o instanceof final TrivialMapPropertiesToNullIfNotNeeded other))
return false;
if (!other.canEqual(this))
return false;
final Object this$id = this.getId();
final Object other$id = other.getId();
if (this$id == null ? other$id != null : !this$id.equals(other$id)) return false;
if (!Objects.equals(this$id, other$id))
return false;
if (this.getAge() != other.getAge()) return false;
final Object this$phone = this.getPhone();
final Object other$phone = other.getPhone();
if (this$phone == null ? other$phone != null : !this$phone.equals(other$phone)) return false;
if (!Objects.equals(this$phone, other$phone))
return false;
final Object this$isSupreme = this.getIsSupreme();
final Object other$isSupreme = other.getIsSupreme();
if (this$isSupreme == null ? other$isSupreme != null : !this$isSupreme.equals(other$isSupreme))
if (!Objects.equals(this$isSupreme, other$isSupreme))
return false;
if (this.getReferenceToCustomer() != other.getReferenceToCustomer()) return false;
return true;
return this.getReferenceToCustomer() == other.getReferenceToCustomer();
}
protected boolean canEqual(final Object other) {
@@ -814,20 +819,21 @@ public class EntityRowMapperUnitTests {
public boolean equals(final Object o) {
if (o == this) return true;
if (!(o instanceof WithReference)) return false;
final WithReference other = (WithReference) o;
if (!other.canEqual((Object) this)) return false;
if (!(o instanceof final WithReference other))
return false;
if (!other.canEqual(this))
return false;
final Object this$id = this.getId();
final Object other$id = other.getId();
if (this$id == null ? other$id != null : !this$id.equals(other$id)) return false;
if (!Objects.equals(this$id, other$id))
return false;
final Object this$name = this.getName();
final Object other$name = other.getName();
if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
if (!Objects.equals(this$name, other$name))
return false;
final Object this$trivialId = this.getTrivialId();
final Object other$trivialId = other.getTrivialId();
if (this$trivialId == null ? other$trivialId != null : !this$trivialId.equals(other$trivialId))
return false;
return true;
return Objects.equals(this$trivialId, other$trivialId);
}
protected boolean canEqual(final Object other) {
@@ -1059,7 +1065,7 @@ public class EntityRowMapperUnitTests {
doReturn(simpleEntriesWithInts).when(accessStrategy)
.findAllByPath(identifierOfValue(ID_FOR_ENTITY_REFERENCING_LIST), any(PersistentPropertyPath.class));
BasicJdbcConverter converter = new BasicJdbcConverter(context, accessStrategy, new JdbcCustomConversions(),
MappingJdbcConverter converter = new MappingJdbcConverter(context, accessStrategy, new JdbcCustomConversions(),
JdbcTypeFactory.unsupported(), IdentifierProcessing.ANSI);
return new EntityRowMapper<>( //
@@ -1109,7 +1115,7 @@ public class EntityRowMapperUnitTests {
private static class ResultSetAnswer implements Answer<Object> {
private List<String> names;
private final List<String> names;
private final List<Map<String, Object>> values;
private int index = -1;
@@ -1314,10 +1320,10 @@ public class EntityRowMapperUnitTests {
private static class FixtureBuilder<T> implements SetValue<T>, SetColumns<T>, SetExpectation<T> {
private List<Object> values = new ArrayList<>();
private List<String> columns = new ArrayList<>();
private final List<Object> values = new ArrayList<>();
private final List<String> columns = new ArrayList<>();
private String explainingColumn;
private List<Expectation<T>> expectations = new ArrayList<>();
private final List<Expectation<T>> expectations = new ArrayList<>();
@Override
public SetColumns<T> value(Object value) {

View File

@@ -36,7 +36,7 @@ import org.springframework.data.relational.core.mapping.AggregatePath;
public class JdbcIdentifierBuilderUnitTests {
JdbcMappingContext context = new JdbcMappingContext();
JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
JdbcConverter converter = new MappingJdbcConverter(context, (identifier, path) -> {
throw new UnsupportedOperationException();
});

View File

@@ -45,15 +45,15 @@ import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.data.util.TypeInformation;
/**
* Unit tests for {@link BasicJdbcConverter}.
* Unit tests for {@link MappingJdbcConverter}.
*
* @author Mark Paluch
*/
public class BasicJdbcConverterUnitTests {
public class MappingJdbcConverterUnitTests {
JdbcMappingContext context = new JdbcMappingContext();
StubbedJdbcTypeFactory typeFactory = new StubbedJdbcTypeFactory();
BasicJdbcConverter converter = new BasicJdbcConverter( //
MappingJdbcConverter converter = new MappingJdbcConverter( //
context, //
(identifier, path) -> {
throw new UnsupportedOperationException();

View File

@@ -28,7 +28,6 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.core.dialect.PostgresDialect;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.sql.Condition;
@@ -48,7 +47,7 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
public class QueryMapperUnitTests {
JdbcMappingContext context = new JdbcMappingContext();
JdbcConverter converter = new BasicJdbcConverter(context, mock(RelationResolver.class));
JdbcConverter converter = new MappingJdbcConverter(context, mock(RelationResolver.class));
QueryMapper mapper = new QueryMapper(converter);
MapSqlParameterSource parameterSource = new MapSqlParameterSource();

View File

@@ -23,7 +23,6 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.PersistentPropertyPathTestUtils;
@@ -214,7 +213,7 @@ public class SqlGeneratorContextBasedNamingStrategyUnitTests {
private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) {
RelationalMappingContext context = new JdbcMappingContext(namingStrategy);
JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
JdbcConverter converter = new MappingJdbcConverter(context, (identifier, path) -> {
throw new UnsupportedOperationException();
});
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class);

View File

@@ -42,7 +42,7 @@ import org.springframework.lang.Nullable;
public class SqlGeneratorEmbeddedUnitTests {
private final RelationalMappingContext context = new JdbcMappingContext();
JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
JdbcConverter converter = new MappingJdbcConverter(context, (identifier, path) -> {
throw new UnsupportedOperationException();
});
private SqlGenerator sqlGenerator;

View File

@@ -19,7 +19,6 @@ import static org.assertj.core.api.Assertions.*;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.PersistentPropertyPathTestUtils;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
@@ -197,7 +196,7 @@ class SqlGeneratorFixedNamingStrategyUnitTests {
private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) {
context = new JdbcMappingContext(namingStrategy);
JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
JdbcConverter converter = new MappingJdbcConverter(context, (identifier, path) -> {
throw new UnsupportedOperationException();
});
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class);

View File

@@ -79,7 +79,7 @@ class SqlGeneratorUnitTests {
private final PrefixingNamingStrategy namingStrategy = new PrefixingNamingStrategy();
private RelationalMappingContext context = new JdbcMappingContext(namingStrategy);
private final JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
private final JdbcConverter converter = new MappingJdbcConverter(context, (identifier, path) -> {
throw new UnsupportedOperationException();
});
private SqlGenerator sqlGenerator;

View File

@@ -24,6 +24,7 @@ import static org.springframework.data.jdbc.core.convert.DefaultDataAccessStrate
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import org.junit.jupiter.api.Test;
import org.springframework.core.convert.converter.Converter;
@@ -47,7 +48,7 @@ class SqlParametersFactoryTest {
RelationalMappingContext context = new JdbcMappingContext();
RelationResolver relationResolver = mock(RelationResolver.class);
BasicJdbcConverter converter = new BasicJdbcConverter(context, relationResolver);
MappingJdbcConverter converter = new MappingJdbcConverter(context, relationResolver);
AnsiDialect dialect = AnsiDialect.INSTANCE;
SqlParametersFactory sqlParametersFactory = new SqlParametersFactory(context, converter);
@@ -210,14 +211,11 @@ class SqlParametersFactoryTest {
public boolean equals(final Object o) {
if (o == this)
return true;
if (!(o instanceof IdValue))
if (!(o instanceof final IdValue other))
return false;
final IdValue other = (IdValue) o;
final Object this$id = this.getId();
final Object other$id = other.getId();
if (this$id == null ? other$id != null : !this$id.equals(other$id))
return false;
return true;
return Objects.equals(this$id, other$id);
}
public int hashCode() {
@@ -301,7 +299,7 @@ class SqlParametersFactoryTest {
private SqlParametersFactory createSqlParametersFactoryWithConverters(List<?> converters) {
BasicJdbcConverter converter = new BasicJdbcConverter(context, relationResolver,
MappingJdbcConverter converter = new MappingJdbcConverter(context, relationResolver,
new JdbcCustomConversions(converters), new DefaultJdbcTypeFactory(mock(JdbcOperations.class)),
dialect.getIdentifierProcessing());
return new SqlParametersFactory(context, converter);

View File

@@ -33,8 +33,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
import org.springframework.data.jdbc.testing.DatabaseType;
@@ -116,7 +116,7 @@ public class MyBatisCustomizingNamespaceHsqlIntegrationTests {
MyBatisDataAccessStrategy dataAccessStrategy(SqlSession sqlSession) {
RelationalMappingContext context = new JdbcMappingContext();
JdbcConverter converter = new BasicJdbcConverter(context, (Identifier, path) -> null);
JdbcConverter converter = new MappingJdbcConverter(context, (Identifier, path) -> null);
MyBatisDataAccessStrategy strategy = new MyBatisDataAccessStrategy(sqlSession,
HsqlDbDialect.INSTANCE.getIdentifierProcessing());

View File

@@ -33,15 +33,7 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory;
import org.springframework.data.jdbc.core.convert.DelegatingDataAccessStrategy;
import org.springframework.data.jdbc.core.convert.InsertStrategyFactory;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.core.convert.SqlParametersFactory;
import org.springframework.data.jdbc.core.convert.*;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository;
@@ -67,6 +59,7 @@ import org.springframework.jdbc.support.KeyHolder;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* Unit tests for application events via {@link SimpleJdbcRepository}.
*
@@ -95,8 +88,8 @@ class SimpleJdbcRepositoryEventsUnitTests {
Dialect dialect = HsqlDbDialect.INSTANCE;
DelegatingDataAccessStrategy delegatingDataAccessStrategy = new DelegatingDataAccessStrategy();
JdbcConverter converter = new BasicJdbcConverter(context, delegatingDataAccessStrategy, new JdbcCustomConversions(),
new DefaultJdbcTypeFactory(operations.getJdbcOperations()), dialect.getIdentifierProcessing());
JdbcConverter converter = new MappingJdbcConverter(context, delegatingDataAccessStrategy,
new JdbcCustomConversions(), new DefaultJdbcTypeFactory(operations.getJdbcOperations()));
SqlGeneratorSource generatorSource = new SqlGeneratorSource(context, converter, dialect);
SqlParametersFactory sqlParametersFactory = new SqlParametersFactory(context, converter);
InsertStrategyFactory insertStrategyFactory = new InsertStrategyFactory(operations, dialect);

View File

@@ -30,8 +30,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
import org.springframework.data.jdbc.core.convert.RelationResolver;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
@@ -70,7 +70,7 @@ public class PartTreeJdbcQueryUnitTests {
private static final String BASE_SELECT = "SELECT " + ALL_FIELDS + " " + JOIN_CLAUSE;
JdbcMappingContext mappingContext = new JdbcMappingContext();
JdbcConverter converter = new BasicJdbcConverter(mappingContext, mock(RelationResolver.class));
JdbcConverter converter = new MappingJdbcConverter(mappingContext, mock(RelationResolver.class));
ReturnedType returnedType = mock(ReturnedType.class);
@Test // DATAJDBC-318

View File

@@ -39,15 +39,14 @@ import org.springframework.data.convert.WritingConverter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.core.convert.JdbcTypeFactory;
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
import org.springframework.data.jdbc.core.convert.RelationResolver;
import org.springframework.data.jdbc.core.mapping.JdbcValue;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
@@ -86,7 +85,7 @@ class StringBasedJdbcQueryUnitTests {
this.defaultRowMapper = mock(RowMapper.class);
this.operations = mock(NamedParameterJdbcOperations.class);
this.context = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS);
this.converter = new BasicJdbcConverter(context, mock(RelationResolver.class));
this.converter = new MappingJdbcConverter(context, mock(RelationResolver.class));
this.evaluationContextProvider = mock(QueryMethodEvaluationContextProvider.class);
}
@@ -242,7 +241,7 @@ class StringBasedJdbcQueryUnitTests {
private final JdbcQueryMethod method;
private Object[] arguments;
private BasicJdbcConverter converter;
private MappingJdbcConverter converter;
public QueryFixture(JdbcQueryMethod method) {
this.method = method;
@@ -257,8 +256,8 @@ class StringBasedJdbcQueryUnitTests {
public SqlParameterSource extractParameterSource() {
BasicJdbcConverter converter = this.converter == null //
? new BasicJdbcConverter(mock(RelationalMappingContext.class), //
MappingJdbcConverter converter = this.converter == null //
? new MappingJdbcConverter(mock(RelationalMappingContext.class), //
mock(RelationResolver.class))
: this.converter;
@@ -273,7 +272,7 @@ class StringBasedJdbcQueryUnitTests {
return captor.getValue();
}
public QueryFixture withConverter(BasicJdbcConverter converter) {
public QueryFixture withConverter(MappingJdbcConverter converter) {
this.converter = converter;
@@ -282,8 +281,8 @@ class StringBasedJdbcQueryUnitTests {
public QueryFixture withCustomConverters(Object... converters) {
return withConverter(new BasicJdbcConverter(mock(RelationalMappingContext.class), mock(RelationResolver.class),
new JdbcCustomConversions(List.of(converters)), JdbcTypeFactory.unsupported(), IdentifierProcessing.ANSI));
return withConverter(new MappingJdbcConverter(mock(RelationalMappingContext.class), mock(RelationResolver.class),
new JdbcCustomConversions(List.of(converters)), JdbcTypeFactory.unsupported()));
}
}

View File

@@ -34,9 +34,9 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
import org.springframework.data.relational.core.dialect.Dialect;
@@ -89,7 +89,7 @@ public class JdbcRepositoryFactoryBeanUnitTests {
factoryBean.setDataAccessStrategy(dataAccessStrategy);
factoryBean.setMappingContext(mappingContext);
factoryBean.setConverter(new BasicJdbcConverter(mappingContext, dataAccessStrategy));
factoryBean.setConverter(new MappingJdbcConverter(mappingContext, dataAccessStrategy));
factoryBean.setApplicationEventPublisher(publisher);
factoryBean.setBeanFactory(beanFactory);
factoryBean.setDialect(dialect);
@@ -115,7 +115,7 @@ public class JdbcRepositoryFactoryBeanUnitTests {
public void afterPropertiesSetDefaultsNullablePropertiesCorrectly() {
factoryBean.setMappingContext(mappingContext);
factoryBean.setConverter(new BasicJdbcConverter(mappingContext, dataAccessStrategy));
factoryBean.setConverter(new MappingJdbcConverter(mappingContext, dataAccessStrategy));
factoryBean.setApplicationEventPublisher(publisher);
factoryBean.setBeanFactory(beanFactory);
factoryBean.setDialect(dialect);

View File

@@ -157,12 +157,11 @@ public class TestConfiguration {
JdbcArrayColumns arrayColumns = dialect instanceof JdbcDialect ? ((JdbcDialect) dialect).getArraySupport()
: JdbcArrayColumns.DefaultSupport.INSTANCE;
return new BasicJdbcConverter( //
return new MappingJdbcConverter( //
mappingContext, //
relationResolver, //
conversions, //
new DefaultJdbcTypeFactory(template.getJdbcOperations(), arrayColumns), //
dialect.getIdentifierProcessing());
new DefaultJdbcTypeFactory(template.getJdbcOperations(), arrayColumns));
}
@Bean

View File

@@ -31,26 +31,15 @@ import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mapping.IdentifierAccessor;
import org.springframework.data.mapping.InstanceCreatorMetadata;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.SpELContext;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.data.r2dbc.support.ArrayUtils;
import org.springframework.data.relational.core.conversion.MappingRelationalConverter;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.dialect.ArrayColumns;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
@@ -117,36 +106,13 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements
return getConversionService().convert(row, rawType);
}
return read(getRequiredPersistentEntity(type), row, metadata);
}
private <R> R read(RelationalPersistentEntity<R> entity, Row row, @Nullable RowMetadata metadata) {
R result = createInstance(row, metadata, "", entity);
if (entity.requiresPropertyPopulation()) {
ConvertingPropertyAccessor<R> propertyAccessor = new ConvertingPropertyAccessor<>(
entity.getPropertyAccessor(result), getConversionService());
for (RelationalPersistentProperty property : entity) {
if (entity.isCreatorArgument(property)) {
continue;
}
Object value = readFrom(row, metadata, property, "");
if (value != null) {
propertyAccessor.setProperty(property, value);
}
}
}
return result;
RowDocument document = toRowDocument(type, row, metadata != null ? metadata.getColumnMetadatas() : null);
return read(type, document);
}
@Override
public RowDocument toRowDocument(Class<?> type, Readable row, Iterable<? extends ReadableMetadata> metadata) {
public RowDocument toRowDocument(Class<?> type, Readable row,
@Nullable Iterable<? extends ReadableMetadata> metadata) {
RowDocument document = new RowDocument();
RelationalPersistentEntity<?> persistentEntity = getMappingContext().getPersistentEntity(type);
@@ -155,26 +121,28 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements
captureRowValues(row, metadata, document, persistentEntity);
}
for (ReadableMetadata m : metadata) {
if (metadata != null) {
for (ReadableMetadata m : metadata) {
if (document.containsKey(m.getName())) {
continue;
if (document.containsKey(m.getName())) {
continue;
}
document.put(m.getName(), row.get(m.getName()));
}
document.put(m.getName(), row.get(m.getName()));
}
return document;
}
private static void captureRowValues(Readable row, Iterable<? extends ReadableMetadata> metadata,
private static void captureRowValues(Readable row, @Nullable Iterable<? extends ReadableMetadata> metadata,
RowDocument document, RelationalPersistentEntity<?> persistentEntity) {
for (RelationalPersistentProperty property : persistentEntity) {
String identifier = property.getColumnName().getReference();
if (property.isEntity() || !RowMetadataUtils.containsColumn(metadata, identifier)) {
if (property.isEntity() || (metadata != null && !RowMetadataUtils.containsColumn(metadata, identifier))) {
continue;
}
@@ -193,194 +161,6 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements
}
}
/**
* Read a single value or a complete Entity from the {@link Row} passed as an argument.
*
* @param row the {@link Row} to extract the value from. Must not be {@literal null}.
* @param metadata the {@link RowMetadata}. Can be {@literal null}.
* @param property the {@link RelationalPersistentProperty} for which the value is intended. Must not be
* {@literal null}.
* @param prefix to be used for all column names accessed by this method. Must not be {@literal null}.
* @return the value read from the {@link Row}. May be {@literal null}.
*/
@Nullable
private Object readFrom(Row row, @Nullable RowMetadata metadata, RelationalPersistentProperty property,
String prefix) {
String identifier = prefix + property.getColumnName().getReference();
try {
Object value = null;
if (metadata == null || RowMetadataUtils.containsColumn(metadata, identifier)) {
if (property.getType().equals(Clob.class)) {
value = row.get(identifier, Clob.class);
} else if (property.getType().equals(Blob.class)) {
value = row.get(identifier, Blob.class);
} else {
value = row.get(identifier);
}
}
if (value == null) {
return null;
}
if (getConversions().hasCustomReadTarget(value.getClass(), property.getType())) {
return readValue(value, property.getTypeInformation());
}
if (property.isEntity()) {
return readEntityFrom(row, metadata, property);
}
return readValue(value, property.getTypeInformation());
} catch (Exception o_O) {
throw new MappingException(String.format("Could not read property %s from column %s", property, identifier), o_O);
}
}
public Object readValue(@Nullable Object value, TypeInformation<?> type) {
if (null == value) {
return null;
}
if (getConversions().hasCustomReadTarget(value.getClass(), type.getType())) {
return getConversionService().convert(value, type.getType());
} else if (value instanceof Collection || value.getClass().isArray()) {
return readCollectionOrArray(asCollection(value), type);
} else {
return getPotentiallyConvertedSimpleRead(value, type.getType());
}
}
/**
* Reads the given value into a collection of the given {@link TypeInformation}.
*
* @param source must not be {@literal null}.
* @param targetType must not be {@literal null}.
* @return the converted {@link Collection} or array, will never be {@literal null}.
*/
@SuppressWarnings("unchecked")
private Object readCollectionOrArray(Collection<?> source, TypeInformation<?> targetType) {
Assert.notNull(targetType, "Target type must not be null");
Class<?> collectionType = targetType.isSubTypeOf(Collection.class) //
? targetType.getType() //
: List.class;
TypeInformation<?> componentType = targetType.getComponentType() != null //
? targetType.getComponentType() //
: TypeInformation.OBJECT;
Class<?> rawComponentType = componentType.getType();
Collection<Object> items = targetType.getType().isArray() //
? new ArrayList<>(source.size()) //
: CollectionFactory.createCollection(collectionType, rawComponentType, source.size());
if (source.isEmpty()) {
return getPotentiallyConvertedSimpleRead(items, targetType.getType());
}
for (Object element : source) {
if (!Object.class.equals(rawComponentType) && element instanceof Collection) {
if (!rawComponentType.isArray() && !ClassUtils.isAssignable(Iterable.class, rawComponentType)) {
throw new MappingException(String.format(
"Cannot convert %1$s of type %2$s into an instance of %3$s; Implement a custom Converter<%2$s, %3$s> and register it with the CustomConversions",
element, element.getClass(), rawComponentType));
}
}
if (element instanceof List) {
items.add(readCollectionOrArray((Collection<Object>) element, componentType));
} else {
items.add(getPotentiallyConvertedSimpleRead(element, rawComponentType));
}
}
return getPotentiallyConvertedSimpleRead(items, targetType.getType());
}
/**
* 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 (getConversions().hasCustomReadTarget(value.getClass(), target)) {
return getConversionService().convert(value, target);
}
if (Enum.class.isAssignableFrom(target)) {
return Enum.valueOf((Class<Enum>) target, value.toString());
}
return getConversionService().convert(value, target);
}
@SuppressWarnings("unchecked")
private <S> S readEntityFrom(Row row, @Nullable RowMetadata metadata, PersistentProperty<?> property) {
String prefix = property.getName() + "_";
RelationalPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(property.getActualType());
if (entity.hasIdProperty()) {
if (readFrom(row, metadata, entity.getRequiredIdProperty(), prefix) == null) {
return null;
}
}
Object instance = createInstance(row, metadata, prefix, entity);
if (entity.requiresPropertyPopulation()) {
PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(instance);
ConvertingPropertyAccessor<?> propertyAccessor = new ConvertingPropertyAccessor<>(accessor,
getConversionService());
for (RelationalPersistentProperty p : entity) {
if (!entity.isCreatorArgument(property)) {
propertyAccessor.setProperty(p, readFrom(row, metadata, p, prefix));
}
}
}
return (S) instance;
}
private <S> S createInstance(Row row, @Nullable RowMetadata rowMetadata, String prefix,
RelationalPersistentEntity<S> entity) {
InstanceCreatorMetadata<RelationalPersistentProperty> persistenceConstructor = entity.getInstanceCreatorMetadata();
ParameterValueProvider<RelationalPersistentProperty> provider;
if (persistenceConstructor != null && persistenceConstructor.hasParameters()) {
SpELContext spELContext = new SpELContext(new RowPropertyAccessor(rowMetadata));
SpELExpressionEvaluator expressionEvaluator = new DefaultSpELExpressionEvaluator(row, spELContext);
provider = new SpELExpressionParameterValueProvider<>(expressionEvaluator, getConversionService(),
new RowParameterValueProvider(row, rowMetadata, entity, this, prefix));
} else {
provider = NoOpParameterValueProvider.INSTANCE;
}
return createInstance(entity, provider::getParameterValue);
}
// ----------------------------------
// Entity writing
// ----------------------------------
@@ -732,57 +512,4 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements
return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source);
}
enum NoOpParameterValueProvider implements ParameterValueProvider<RelationalPersistentProperty> {
INSTANCE;
@Override
public <T> T getParameterValue(
org.springframework.data.mapping.Parameter<T, RelationalPersistentProperty> parameter) {
return null;
}
}
private class RowParameterValueProvider implements ParameterValueProvider<RelationalPersistentProperty> {
private final Row resultSet;
private final RowMetadata metadata;
private final RelationalPersistentEntity<?> entity;
private final RelationalConverter converter;
private final String prefix;
public RowParameterValueProvider(Row resultSet, RowMetadata metadata, RelationalPersistentEntity<?> entity,
RelationalConverter converter, String prefix) {
this.resultSet = resultSet;
this.metadata = metadata;
this.entity = entity;
this.converter = converter;
this.prefix = prefix;
}
@Override
@Nullable
public <T> T getParameterValue(
org.springframework.data.mapping.Parameter<T, RelationalPersistentProperty> parameter) {
RelationalPersistentProperty property = this.entity.getRequiredPersistentProperty(parameter.getName());
Object value = readFrom(this.resultSet, this.metadata, property, this.prefix);
if (value == null) {
return null;
}
Class<T> type = parameter.getType().getType();
if (type.isInstance(value)) {
return type.cast(value);
}
try {
return this.converter.getConversionService().convert(value, type);
} catch (Exception o_O) {
throw new MappingException(String.format("Couldn't read parameter %s", parameter.getName()), o_O);
}
}
}
}

View File

@@ -25,11 +25,9 @@ import java.util.function.BiFunction;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.convert.EntityWriter;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.dialect.ArrayColumns;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.RowDocument;
@@ -42,13 +40,6 @@ import org.springframework.data.relational.domain.RowDocument;
public interface R2dbcConverter
extends EntityReader<Object, Row>, EntityWriter<Object, OutboundRow>, 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.
*

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2023 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
*
* https://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 java.util.Collections;
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.mapping.context.MappingContext;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.util.Assert;
/**
* Base class for {@link RelationalConverter} implementations. Sets up a {@link ConfigurableConversionService} and
* populates basic converters. Allows registering {@link CustomConversions}.
*
* @author Mark Paluch
* @since 3.2
*/
public abstract class AbstractRelationalConverter implements RelationalConverter {
private final RelationalMappingContext context;
private final ConfigurableConversionService conversionService;
private final EntityInstantiators entityInstantiators;
private final CustomConversions conversions;
/**
* Creates a new {@link AbstractRelationalConverter} given {@link MappingContext}.
*
* @param context must not be {@literal null}.
*/
public AbstractRelationalConverter(RelationalMappingContext context) {
this(context, new CustomConversions(StoreConversions.NONE, Collections.emptyList()), new DefaultConversionService(),
new EntityInstantiators());
}
/**
* Creates a new {@link AbstractRelationalConverter} given {@link MappingContext} and {@link CustomConversions}.
*
* @param context must not be {@literal null}.
* @param conversions must not be {@literal null}.
*/
public AbstractRelationalConverter(RelationalMappingContext context, CustomConversions conversions) {
this(context, conversions, new DefaultConversionService(), new EntityInstantiators());
}
@SuppressWarnings("unchecked")
private AbstractRelationalConverter(RelationalMappingContext 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 = context;
this.conversionService = conversionService;
this.entityInstantiators = entityInstantiators;
this.conversions = conversions;
conversions.registerConvertersIn(this.conversionService);
}
@Override
public ConversionService getConversionService() {
return conversionService;
}
public CustomConversions getConversions() {
return conversions;
}
@Override
public EntityInstantiators getEntityInstantiators() {
return entityInstantiators;
}
@Override
public RelationalMappingContext getMappingContext() {
return context;
}
}

View File

@@ -15,40 +15,10 @@
*/
package org.springframework.data.relational.core.conversion;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
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.mapping.Parameter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.projection.EntityProjection;
import org.springframework.data.projection.ProjectionFactory;
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.domain.RowDocument;
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
@@ -63,13 +33,11 @@ import org.springframework.util.ClassUtils;
* @see MappingContext
* @see SimpleTypeHolder
* @see CustomConversions
* @deprecated since 3.2, use {@link MappingRelationalConverter} instead as the naming suggests a limited scope of
* functionality.
*/
public class BasicRelationalConverter implements RelationalConverter {
private final RelationalMappingContext context;
private final ConfigurableConversionService conversionService;
private final EntityInstantiators entityInstantiators;
private final CustomConversions conversions;
@Deprecated(since = "3.2")
public class BasicRelationalConverter extends MappingRelationalConverter {
/**
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext}.
@@ -77,8 +45,7 @@ public class BasicRelationalConverter implements RelationalConverter {
* @param context must not be {@literal null}.
*/
public BasicRelationalConverter(RelationalMappingContext context) {
this(context, new CustomConversions(StoreConversions.NONE, Collections.emptyList()), new DefaultConversionService(),
new EntityInstantiators());
super(context);
}
/**
@@ -88,259 +55,7 @@ public class BasicRelationalConverter implements RelationalConverter {
* @param conversions must not be {@literal null}.
*/
public BasicRelationalConverter(RelationalMappingContext context, CustomConversions conversions) {
this(context, conversions, new DefaultConversionService(), new EntityInstantiators());
super(context, conversions);
}
@SuppressWarnings("unchecked")
private BasicRelationalConverter(RelationalMappingContext 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 = context;
this.conversionService = conversionService;
this.entityInstantiators = entityInstantiators;
this.conversions = conversions;
conversions.registerConvertersIn(this.conversionService);
}
@Override
public ConversionService getConversionService() {
return conversionService;
}
public CustomConversions getConversions() {
return conversions;
}
@Override
public RelationalMappingContext getMappingContext() {
return context;
}
@Override
public <T> PersistentPropertyPathAccessor<T> getPropertyAccessor(PersistentEntity<T, ?> persistentEntity,
T instance) {
PersistentPropertyPathAccessor<T> accessor = persistentEntity.getPropertyPathAccessor(instance);
return new ConvertingPropertyAccessor<>(accessor, conversionService);
}
@Override
public <M, D> EntityProjection<M, D> introspectProjection(Class<M> resultType, Class<D> entityType) {
throw new UnsupportedOperationException();
}
@Override
public ProjectionFactory getProjectionFactory() {
throw new UnsupportedOperationException();
}
@Override
public <R> R project(EntityProjection<R, ?> descriptor, RowDocument document) {
throw new UnsupportedOperationException();
}
@Override
public <R> R read(Class<R> type, RowDocument source) {
throw new UnsupportedOperationException();
}
@Override
public <T> T createInstance(PersistentEntity<T, RelationalPersistentProperty> entity,
Function<Parameter<?, RelationalPersistentProperty>, Object> parameterValueProvider) {
return entityInstantiators.getInstantiatorFor(entity) //
.createInstance(entity, new ConvertingParameterValueProvider<>(parameterValueProvider));
}
@Override
@Nullable
public Object readValue(@Nullable Object value, TypeInformation<?> type) {
if (null == value) {
return null;
}
if (getConversions().hasCustomReadTarget(value.getClass(), type.getType())) {
TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(value.getClass());
TypeDescriptor targetDescriptor = createTypeDescriptor(type);
return getConversionService().convert(value, sourceDescriptor, targetDescriptor);
}
return getPotentiallyConvertedSimpleRead(value, type);
}
@Override
@Nullable
public Object writeValue(@Nullable Object value, TypeInformation<?> type) {
if (value == null) {
return null;
}
if (getConversions().isSimpleType(value.getClass())) {
if (TypeInformation.OBJECT != type && conversionService.canConvert(value.getClass(), type.getType())) {
value = conversionService.convert(value, type.getType());
}
return getPotentiallyConvertedSimpleWrite(value);
}
if (value.getClass().isArray()) {
return writeArray(value, type);
}
if (value instanceof Collection<?>) {
return writeCollection((Iterable<?>) value, type);
}
RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(value.getClass());
if (persistentEntity != null) {
Object id = persistentEntity.getIdentifierAccessor(value).getIdentifier();
return writeValue(id, type);
}
return conversionService.convert(value, type.getType());
}
private Object writeArray(Object value, TypeInformation<?> type) {
Class<?> componentType = value.getClass().getComponentType();
Optional<Class<?>> optionalWriteTarget = getConversions().getCustomWriteTarget(componentType);
if (optionalWriteTarget.isEmpty() && !componentType.isEnum()) {
return value;
}
Class<?> customWriteTarget = optionalWriteTarget
.orElseGet(() -> componentType.isEnum() ? String.class : componentType);
// optimization: bypass identity conversion
if (customWriteTarget.equals(componentType)) {
return value;
}
TypeInformation<?> component = TypeInformation.OBJECT;
if (type.isCollectionLike() && type.getActualType() != null) {
component = type.getRequiredComponentType();
}
int length = Array.getLength(value);
Object target = Array.newInstance(customWriteTarget, length);
for (int i = 0; i < length; i++) {
Array.set(target, i, writeValue(Array.get(value, i), component));
}
return target;
}
private Object writeCollection(Iterable<?> value, TypeInformation<?> type) {
List<Object> mapped = new ArrayList<>();
TypeInformation<?> component = TypeInformation.OBJECT;
if (type.isCollectionLike() && type.getActualType() != null) {
component = type.getRequiredComponentType();
}
for (Object o : value) {
mapped.add(writeValue(o, component));
}
if (type.getType().isInstance(mapped) || !type.isCollectionLike()) {
return mapped;
}
return conversionService.convert(mapped, type.getType());
}
@Override
public EntityInstantiators getEntityInstantiators() {
return this.entityInstantiators;
}
/**
* 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 to be converted. Must not be {@code null}.
* @return the converted value if a conversion applies or the original value. Might return {@code null}.
*/
@Nullable
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 to be converted. May be {@code null}..
* @param type {@link TypeInformation} into which the value is to be converted. Must not be {@code null}.
* @return the converted value if a conversion applies or the original value. Might return {@code null}.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected Object getPotentiallyConvertedSimpleRead(Object value, TypeInformation<?> type) {
Class<?> target = type.getType();
if (ClassUtils.isAssignableValue(target, value)) {
return value;
}
if (Enum.class.isAssignableFrom(target) && value instanceof CharSequence) {
return Enum.valueOf((Class<Enum>) target, value.toString());
}
return conversionService.convert(value, TypeDescriptor.forObject(value), createTypeDescriptor(type));
}
private static TypeDescriptor createTypeDescriptor(TypeInformation<?> type) {
List<TypeInformation<?>> typeArguments = type.getTypeArguments();
Class<?>[] generics = new Class[typeArguments.size()];
for (int i = 0; i < typeArguments.size(); i++) {
generics[i] = typeArguments.get(i).getType();
}
return new TypeDescriptor(ResolvableType.forClassWithGenerics(type.getType(), generics), type.getType(), null);
}
/**
* Converter-aware {@link ParameterValueProvider}.
*
* @param <P>
* @author Mark Paluch
*/
class ConvertingParameterValueProvider<P extends PersistentProperty<P>> implements ParameterValueProvider<P> {
private final Function<Parameter<?, P>, Object> delegate;
ConvertingParameterValueProvider(Function<Parameter<?, P>, Object> delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
}
@Override
@SuppressWarnings("unchecked")
public <T> T getParameterValue(Parameter<T, P> parameter) {
return (T) readValue(delegate.apply(parameter), parameter.getType());
}
}
}

View File

@@ -15,18 +15,23 @@
*/
package org.springframework.data.relational.core.conversion;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.CollectionFactory;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mapping.InstanceCreatorMetadata;
import org.springframework.data.mapping.MappingException;
@@ -34,6 +39,7 @@ import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
@@ -41,6 +47,7 @@ import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.mapping.model.SpELContext;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
@@ -69,9 +76,15 @@ import org.springframework.util.ClassUtils;
* {@link RowDocument}.
*
* @author Mark Paluch
* @author Jens Schauder
* @author Chirag Tailor
* @author Vincent Galloy
* @see MappingContext
* @see SimpleTypeHolder
* @see CustomConversions
* @since 3.2
*/
public class MappingRelationalConverter extends BasicRelationalConverter implements ApplicationContextAware {
public class MappingRelationalConverter extends AbstractRelationalConverter implements ApplicationContextAware {
private SpELContext spELContext;
@@ -124,11 +137,6 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
this.projectionFactory.setBeanClassLoader(applicationContext.getClassLoader());
}
@Override
public ProjectionFactory getProjectionFactory() {
return this.projectionFactory;
}
/**
* Creates a new {@link ConversionContext}.
*
@@ -142,6 +150,14 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
this::readMap, this::getPotentiallyConvertedSimpleRead);
}
@Override
public <T> PersistentPropertyPathAccessor<T> getPropertyAccessor(PersistentEntity<T, ?> persistentEntity,
T instance) {
PersistentPropertyPathAccessor<T> accessor = persistentEntity.getPropertyPathAccessor(instance);
return new ConvertingPropertyAccessor<>(accessor, getConversionService());
}
@Override
public <M, D> EntityProjection<M, D> introspectProjection(Class<M> resultType, Class<D> entityType) {
@@ -315,7 +331,7 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
}
if (RowDocument.class.isAssignableFrom(rawType)) {
return (S) documentAccessor;
return (S) documentAccessor.document();
}
if (typeHint.isMap()) {
@@ -445,6 +461,14 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
return instance;
}
@Override
public <T> T createInstance(PersistentEntity<T, RelationalPersistentProperty> entity,
Function<Parameter<?, RelationalPersistentProperty>, Object> parameterValueProvider) {
return getEntityInstantiators().getInstantiatorFor(entity) //
.createInstance(entity, new ConvertingParameterValueProvider<>(parameterValueProvider));
}
private ParameterValueProvider<RelationalPersistentProperty> getParameterProvider(ConversionContext context,
RelationalPersistentEntity<?> entity, RowDocumentAccessor source, SpELExpressionEvaluator evaluator) {
@@ -568,6 +592,165 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
return false;
}
@Override
@Nullable
public Object readValue(@Nullable Object value, TypeInformation<?> type) {
if (null == value) {
return null;
}
if (getConversions().hasCustomReadTarget(value.getClass(), type.getType())) {
TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(value.getClass());
TypeDescriptor targetDescriptor = createTypeDescriptor(type);
return getConversionService().convert(value, sourceDescriptor, targetDescriptor);
}
return getPotentiallyConvertedSimpleRead(value, type);
}
/**
* 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 to be converted. Must not be {@code null}.
* @return the converted value if a conversion applies or the original value. Might return {@code null}.
*/
@Nullable
private Object getPotentiallyConvertedSimpleWrite(Object value) {
Optional<Class<?>> customTarget = getConversions().getCustomWriteTarget(value.getClass());
if (customTarget.isPresent()) {
return getConversionService().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 to be converted. May be {@code null}..
* @param type {@link TypeInformation} into which the value is to be converted. Must not be {@code null}.
* @return the converted value if a conversion applies or the original value. Might return {@code null}.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected Object getPotentiallyConvertedSimpleRead(Object value, TypeInformation<?> type) {
Class<?> target = type.getType();
if (ClassUtils.isAssignableValue(target, value)) {
return value;
}
if (Enum.class.isAssignableFrom(target) && value instanceof CharSequence) {
return Enum.valueOf((Class<Enum>) target, value.toString());
}
return getConversionService().convert(value, TypeDescriptor.forObject(value), createTypeDescriptor(type));
}
private static TypeDescriptor createTypeDescriptor(TypeInformation<?> type) {
List<TypeInformation<?>> typeArguments = type.getTypeArguments();
Class<?>[] generics = new Class[typeArguments.size()];
for (int i = 0; i < typeArguments.size(); i++) {
generics[i] = typeArguments.get(i).getType();
}
return new TypeDescriptor(ResolvableType.forClassWithGenerics(type.getType(), generics), type.getType(), null);
}
@Override
@Nullable
public Object writeValue(@Nullable Object value, TypeInformation<?> type) {
if (value == null) {
return null;
}
if (getConversions().isSimpleType(value.getClass())) {
if (TypeInformation.OBJECT != type && getConversionService().canConvert(value.getClass(), type.getType())) {
value = getConversionService().convert(value, type.getType());
}
return getPotentiallyConvertedSimpleWrite(value);
}
if (value.getClass().isArray()) {
return writeArray(value, type);
}
if (value instanceof Collection<?>) {
return writeCollection((Iterable<?>) value, type);
}
RelationalPersistentEntity<?> persistentEntity = getMappingContext().getPersistentEntity(value.getClass());
if (persistentEntity != null) {
Object id = persistentEntity.getIdentifierAccessor(value).getIdentifier();
return writeValue(id, type);
}
return getConversionService().convert(value, type.getType());
}
private Object writeArray(Object value, TypeInformation<?> type) {
Class<?> componentType = value.getClass().getComponentType();
Optional<Class<?>> optionalWriteTarget = getConversions().getCustomWriteTarget(componentType);
if (optionalWriteTarget.isEmpty() && !componentType.isEnum()) {
return value;
}
Class<?> customWriteTarget = optionalWriteTarget
.orElseGet(() -> componentType.isEnum() ? String.class : componentType);
// optimization: bypass identity conversion
if (customWriteTarget.equals(componentType)) {
return value;
}
TypeInformation<?> component = TypeInformation.OBJECT;
if (type.isCollectionLike() && type.getActualType() != null) {
component = type.getRequiredComponentType();
}
int length = Array.getLength(value);
Object target = Array.newInstance(customWriteTarget, length);
for (int i = 0; i < length; i++) {
Array.set(target, i, writeValue(Array.get(value, i), component));
}
return target;
}
private Object writeCollection(Iterable<?> value, TypeInformation<?> type) {
List<Object> mapped = new ArrayList<>();
TypeInformation<?> component = TypeInformation.OBJECT;
if (type.isCollectionLike() && type.getActualType() != null) {
component = type.getRequiredComponentType();
}
for (Object o : value) {
mapped.add(writeValue(o, component));
}
if (type.getType().isInstance(mapped) || !type.isCollectionLike()) {
return mapped;
}
return getConversionService().convert(mapped, type.getType());
}
static Predicate<RelationalPersistentProperty> isConstructorArgument(PersistentEntity<?, ?> entity) {
return entity::isCreatorArgument;
}
@@ -805,13 +988,16 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
}
}
// TODO: Docs
/**
* Extended {@link ParameterValueProvider} that can report whether a property value is present and contextualize the
* instance for specific behavior like projection mapping in the context of a property.
*/
protected interface RelationalPropertyValueProvider extends PropertyValueProvider<RelationalPersistentProperty> {
/**
* Determine whether there is a value for the given {@link RelationalPersistentProperty}.
*
* @param property
* @param property the property to check for whether a value is present.
* @return
*/
boolean hasValue(RelationalPersistentProperty property);
@@ -819,7 +1005,7 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
/**
* Contextualize this property value provider.
*
* @param context
* @param context the context to use.
* @return
*/
RelationalPropertyValueProvider withContext(ConversionContext context);
@@ -834,15 +1020,21 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
/**
* Determine whether there is a value for the given {@link AggregatePath}.
*
* @param path
* @param path the path to check for whether a value is present.
* @return
*/
boolean hasValue(AggregatePath path);
/**
* Determine whether there is a value for the given {@link SqlIdentifier}.
*
* @param identifier the path to check for whether a value is present.
* @return
*/
boolean hasValue(SqlIdentifier identifier);
/**
* Returns a value for the given {@link AggregatePath}.
* Return a value for the given {@link AggregatePath}.
*
* @param path will never be {@literal null}.
* @return
@@ -868,9 +1060,13 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
* @author Mark Paluch
* @author Christoph Strobl
*/
protected record DocumentValueProvider(ConversionContext context, RowDocumentAccessor accessor,
SpELExpressionEvaluator evaluator,
SpELContext spELContext) implements RelationalPropertyValueProvider, AggregatePathValueProvider {
protected static final class DocumentValueProvider
implements RelationalPropertyValueProvider, AggregatePathValueProvider {
private final ConversionContext context;
private final RowDocumentAccessor accessor;
private final SpELExpressionEvaluator evaluator;
private final SpELContext spELContext;
/**
* Creates a new {@link RelationalPropertyValueProvider} for the given source and {@link SpELExpressionEvaluator}.
@@ -879,11 +1075,16 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
* @param accessor must not be {@literal null}.
* @param evaluator must not be {@literal null}.
*/
protected DocumentValueProvider {
private DocumentValueProvider(ConversionContext context, RowDocumentAccessor accessor,
SpELExpressionEvaluator evaluator, SpELContext spELContext) {
Assert.notNull(context, "ConversionContext must no be null");
Assert.notNull(accessor, "DocumentAccessor must no be null");
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null");
this.context = context;
this.accessor = accessor;
this.evaluator = evaluator;
this.spELContext = spELContext;
}
@Override
@@ -936,6 +1137,38 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
return context == this.context ? this : new DocumentValueProvider(context, accessor, evaluator, spELContext);
}
public ConversionContext context() {
return context;
}
public RowDocumentAccessor accessor() {
return accessor;
}
}
/**
* Converter-aware {@link ParameterValueProvider}.
*
* @param <P>
* @author Mark Paluch
*/
class ConvertingParameterValueProvider<P extends PersistentProperty<P>> implements ParameterValueProvider<P> {
private final Function<Parameter<?, P>, Object> delegate;
ConvertingParameterValueProvider(Function<Parameter<?, P>, Object> delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
}
@Override
@SuppressWarnings("unchecked")
public <T> T getParameterValue(Parameter<T, P> parameter) {
return (T) readValue(delegate.apply(parameter), parameter.getType());
}
}
/**

View File

@@ -27,7 +27,6 @@ import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.projection.EntityProjection;
import org.springframework.data.projection.EntityProjectionIntrospector;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.domain.RowDocument;
@@ -43,13 +42,6 @@ import org.springframework.lang.Nullable;
*/
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.
*
@@ -58,12 +50,45 @@ public interface RelationalConverter {
ConversionService getConversionService();
/**
* Returns the {@link ProjectionFactory} for this converter.
* Return the underlying {@link EntityInstantiators}.
*
* @return will never be {@literal null}.
* @since 3.2
* @return
* @since 2.3
*/
ProjectionFactory getProjectionFactory();
EntityInstantiators getEntityInstantiators();
/**
* Returns the underlying {@link MappingContext} used by the converter.
*
* @return never {@literal null}
*/
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> getMappingContext();
/**
* Create a new instance of {@link PersistentEntity} given {@link ParameterValueProvider} to obtain constructor
* properties.
*
* @param entity the kind of entity to create. Must not be {@code null}.
* @param parameterValueProvider a function that provides the value to pass to a constructor, given a
* {@link Parameter}. Must not be {@code null}.
* @param <T> the type of entity to create.
* @return the instantiated entity. Guaranteed to be not {@code null}.
* @deprecated since 3.2, use {@link #read} method instead.
*/
@Deprecated(since = "3.2")
default <T> T createInstance(PersistentEntity<T, RelationalPersistentProperty> entity,
Function<Parameter<?, RelationalPersistentProperty>, Object> parameterValueProvider) {
throw new UnsupportedOperationException("Not supported anymore. Use read(…) instead.");
}
/**
* Return a {@link PersistentPropertyAccessor} to access property values of the {@code instance}.
*
* @param persistentEntity the kind of entity to operate on. Must not be {@code null}.
* @param instance the instance to operate on. Must not be {@code null}.
* @return guaranteed to be not {@code null}.
*/
<T> PersistentPropertyPathAccessor<T> getPropertyAccessor(PersistentEntity<T, ?> persistentEntity, T instance);
/**
* Introspect the given {@link Class result type} in the context of the {@link Class entity type} whether the returned
@@ -101,28 +126,6 @@ public interface RelationalConverter {
*/
<R> R read(Class<R> type, RowDocument source);
/**
* Create a new instance of {@link PersistentEntity} given {@link ParameterValueProvider} to obtain constructor
* properties.
*
* @param entity the kind of entity to create. Must not be {@code null}.
* @param parameterValueProvider a function that provides the value to pass to a constructor, given a
* {@link Parameter}. Must not be {@code null}.
* @param <T> the type of entity to create.
* @return the instantiated entity. Guaranteed to be not {@code null}.
*/
<T> T createInstance(PersistentEntity<T, RelationalPersistentProperty> entity,
Function<Parameter<?, RelationalPersistentProperty>, Object> parameterValueProvider);
/**
* Return a {@link PersistentPropertyAccessor} to access property values of the {@code instance}.
*
* @param persistentEntity the kind of entity to operate on. Must not be {@code null}.
* @param instance the instance to operate on. Must not be {@code null}.
* @return guaranteed to be not {@code null}.
*/
<T> PersistentPropertyPathAccessor<T> getPropertyAccessor(PersistentEntity<T, ?> persistentEntity, T instance);
/**
* Read a relational value into the desired {@link TypeInformation destination type}.
*
@@ -143,11 +146,5 @@ public interface RelationalConverter {
@Nullable
Object writeValue(@Nullable Object value, TypeInformation<?> type);
/**
* Return the underlying {@link EntityInstantiators}.
*
* @return
* @since 2.3
*/
EntityInstantiators getEntityInstantiators();
}