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:
committed by
Jens Schauder
parent
4e3120ee0e
commit
5398b94020
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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}.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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), //
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user