diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java index ed67dfdfb4d..8c37babdd0a 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java @@ -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.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 @@ private Identifier getParentKeys(DbAction.WithDependingOn action, JdbcConvert 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); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java index c57125233c5..32d66d0b9d6 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java @@ -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. *

* 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, 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,7 @@ 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, 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); + super(context, relationResolver); } /** @@ -115,719 +54,24 @@ public BasicJdbcConverter(RelationalMappingContext context, RelationResolver rel * @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} - * @param identifierProcessing must not be {@literal null} - * @since 2.0 + * @since 3.2 */ 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); - } - - @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> 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 mapRow(RelationalPersistentEntity entity, ResultSet resultSet, Object key) { - return new ReadingContext(getMappingContext().getAggregatePath(entity), new ResultSetAccessor(resultSet), - Identifier.empty(), key).mapRow(); - } - - @Override - public T mapRow(AggregatePath path, ResultSet resultSet, Identifier identifier, Object key) { - return new ReadingContext(path, new ResultSetAccessor(resultSet), identifier, key).mapRow(); - } - - @Override - public R projectAndResolve(EntityProjection 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 readAndResolve(Class type, RowDocument source, Identifier identifier) { - - RelationalPersistentEntity entity = (RelationalPersistentEntity) 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); + CustomConversions conversions, JdbcTypeFactory typeFactory) { + super(context, relationResolver, conversions, typeFactory); } /** - * {@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 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 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 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 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. + * Creates a new {@link BasicJdbcConverter} given {@link MappingContext}. * - * @param delegate - * @param aggregatePath - * @param identifier + * @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} + * @param identifierProcessing must not be {@literal null} + * @since 2.0 */ - private record ResolvingConversionContext(ConversionContext delegate, AggregatePath aggregatePath, - Identifier identifier) implements ConversionContext { - - @Override - public S convert(Object source, TypeInformation typeHint) { - return delegate.convert(source, typeHint); - } - - @Override - public S convert(Object source, TypeInformation 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 { - - private final RelationalPersistentEntity 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 entity = (RelationalPersistentEntity) 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 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 ReadingContext extendBy(RelationalPersistentProperty property) { - - return new ReadingContext<>( - (RelationalPersistentEntity) 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 propertyAccessor = getPropertyAccessor(entity, instance); - InstanceCreatorMetadata 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 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 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 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 creatorMetadata = entity.getInstanceCreatorMetadata(); - ParameterValueProvider 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 { - - 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 getParameterValue(Parameter 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 { - - INSTANCE; - - @Override - public T getParameterValue(Parameter parameter) { - return null; - } + public BasicJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver, + CustomConversions conversions, JdbcTypeFactory typeFactory, IdentifierProcessing identifierProcessing) { + super(context, relationResolver, conversions, typeFactory, identifierProcessing); } - } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/EntityRowMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/EntityRowMapper.java index 15683746236..b2e960b0ab2 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/EntityRowMapper.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/EntityRowMapper.java @@ -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.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 implements RowMapper { private final RelationalPersistentEntity 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 EntityRowMapper(RelationalPersistentEntity entity, JdbcConverter conve @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; - } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java index f7c48304ce1..6213c4ed969 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java @@ -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 * @return + * @deprecated since 3.2, use {@link #readAndResolve(Class, RowDocument, Identifier)} instead. */ - T mapRow(RelationalPersistentEntity entity, ResultSet resultSet, Object key); + @Deprecated(since = "3.2") + default T mapRow(RelationalPersistentEntity 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 * @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 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 - * @return - */ - 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 - * @return a new instance of the projection return type {@code R}. - * @since 3.2 - * @see #project(EntityProjection, RowDocument) - */ - R projectAndResolve(EntityProjection descriptor, RowDocument document); - /** * Read a {@link RowDocument} into the requested {@link Class aggregate type} and resolve references by looking these * up from {@link RelationResolver}. diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java index 2ec77afe360..05430670eb1 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java @@ -55,14 +55,11 @@ public Map.Entry mapRow(ResultSet rs, int rowNum) throws SQLException 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)); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java new file mode 100644 index 00000000000..90c7bfdafea --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java @@ -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. + *

+ * 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, 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> 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 readAndResolve(Class type, RowDocument source, Identifier identifier) { + + RelationalPersistentEntity entity = (RelationalPersistentEntity) 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 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 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 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 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 convert(Object source, TypeInformation typeHint) { + return delegate.convert(source, typeHint); + } + + @Override + public S convert(Object source, TypeInformation 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; + } + +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentResultSetExtractor.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentResultSetExtractor.java index 45b264050d8..00b10101137 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentResultSetExtractor.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentResultSetExtractor.java @@ -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}. */ diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java index 94666750796..197bb08f665 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java @@ -135,8 +135,7 @@ public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext, NamedParam : 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); } /** diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java index 75a2e4d426c..710746997c0 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java @@ -31,15 +31,15 @@ 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 rootInsert = new DbAction.InsertRoot<>(entity, IdValueSource.GENERATED); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java index 88f644852fc..d56f323058f 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java @@ -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.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 @@ 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 List getList() { 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 Long getId() { 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() { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java index 1a8df7c7884..aeb9f3fd7c6 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java @@ -25,11 +25,11 @@ 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 @@ 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); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java index d95c3e0cd72..90c186b9479 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java @@ -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.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.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); } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java index a53b773a957..68c9611e1eb 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicRelationalConverterAggregateReferenceUnitTests.java @@ -28,15 +28,14 @@ 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); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java index 076e877f508..e97df419ecc 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java @@ -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 @@ void before() { 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), // diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java index c2cba6c5a59..454b28c3a2b 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java @@ -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.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 @@ void columnNamesAreCaseInsensitive() throws SQLException { @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 @@ void missingColumnsInEmbeddedShouldBeUnset() throws SQLException { @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 String getName() { 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 long getReferenceToCustomer() { 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 AggregateReference getTrivialId() { 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 @@ private EntityRowMapper createRowMapper(Class type, NamingStrategy nam 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 @@ private static List> convertValues(List columns, Obj private static class ResultSetAnswer implements Answer { - private List names; + private final List names; private final List> values; private int index = -1; @@ -1314,10 +1320,10 @@ private interface SetExpectation { private static class FixtureBuilder implements SetValue, SetColumns, SetExpectation { - private List values = new ArrayList<>(); - private List columns = new ArrayList<>(); + private final List values = new ArrayList<>(); + private final List columns = new ArrayList<>(); private String explainingColumn; - private List> expectations = new ArrayList<>(); + private final List> expectations = new ArrayList<>(); @Override public SetColumns value(Object value) { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilderUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilderUnitTests.java index f8f237e4305..a6dbc9c7172 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilderUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilderUnitTests.java @@ -36,7 +36,7 @@ public class JdbcIdentifierBuilderUnitTests { JdbcMappingContext context = new JdbcMappingContext(); - JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> { + JdbcConverter converter = new MappingJdbcConverter(context, (identifier, path) -> { throw new UnsupportedOperationException(); }); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverterUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterUnitTests.java similarity index 98% rename from spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverterUnitTests.java rename to spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterUnitTests.java index 4dc22631b20..ee716fea6d5 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverterUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterUnitTests.java @@ -45,15 +45,15 @@ 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(); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/QueryMapperUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/QueryMapperUnitTests.java index d2526fc9f2d..4e04816b306 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/QueryMapperUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/QueryMapperUnitTests.java @@ -28,7 +28,6 @@ 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 @@ 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(); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorContextBasedNamingStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorContextBasedNamingStrategyUnitTests.java index 87510b0a676..eaa4defb95b 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorContextBasedNamingStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorContextBasedNamingStrategyUnitTests.java @@ -23,7 +23,6 @@ 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 @@ private void threadedTest(String user, CountDownLatch latch, Consumer te 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); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java index 25a97b85f6d..a4ab377d437 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java @@ -42,7 +42,7 @@ 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; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java index 5784dc09594..17960b412f1 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java @@ -19,7 +19,6 @@ 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 @@ private PersistentPropertyPath getPath(String path 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); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java index 61a3856ab6f..f2c295828d4 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java @@ -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; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlParametersFactoryTest.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlParametersFactoryTest.java index a54383b3c97..63dfecff10d 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlParametersFactoryTest.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlParametersFactoryTest.java @@ -24,6 +24,7 @@ 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 @@ public String getId() { 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 @@ public WithIllegalCharacters(Long id, String value) { 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); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisCustomizingNamespaceHsqlIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisCustomizingNamespaceHsqlIntegrationTests.java index 37725dcb30a..b86e52f75f6 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisCustomizingNamespaceHsqlIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisCustomizingNamespaceHsqlIntegrationTests.java @@ -33,8 +33,8 @@ 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 @@ SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory factory) { 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()); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java index e94127e7374..a9541421ce1 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java @@ -33,15 +33,7 @@ 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.lang.Nullable; import org.springframework.util.ObjectUtils; + /** * Unit tests for application events via {@link SimpleJdbcRepository}. * @@ -95,8 +88,8 @@ void before() { 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); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java index 8c13c68de6f..b5a03c2a92a 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java @@ -30,8 +30,8 @@ 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 diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java index 30dc8d58600..0f2a3f61761 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java @@ -39,15 +39,14 @@ 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 @@ void setup() { 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 @@ private class QueryFixture { private final JdbcQueryMethod method; private Object[] arguments; - private BasicJdbcConverter converter; + private MappingJdbcConverter converter; public QueryFixture(JdbcQueryMethod method) { this.method = method; @@ -257,8 +256,8 @@ public QueryFixture withArguments(Object... arguments) { 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 @@ public SqlParameterSource extractParameterSource() { return captor.getValue(); } - public QueryFixture withConverter(BasicJdbcConverter converter) { + public QueryFixture withConverter(MappingJdbcConverter converter) { this.converter = converter; @@ -282,8 +281,8 @@ public QueryFixture withConverter(BasicJdbcConverter converter) { 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())); } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java index c7824aaa738..3a882c5dcce 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java @@ -34,9 +34,9 @@ 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 void setsUpBasicInstanceCorrectly() { 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 void afterPropertiesThrowsExceptionWhenNoMappingContextSet() { 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); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java index 26041a201a2..ad6f0b30f81 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java @@ -157,12 +157,11 @@ JdbcConverter relationalConverter(RelationalMappingContext mappingContext, @Lazy 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 diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java index fe13e598de1..4bfba1ca9fe 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java @@ -31,26 +31,15 @@ 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 @@ && getConversionService().canConvert(Row.class, rawType)) { return getConversionService().convert(row, rawType); } - return read(getRequiredPersistentEntity(type), row, metadata); - } - - private R read(RelationalPersistentEntity entity, Row row, @Nullable RowMetadata metadata) { - - R result = createInstance(row, metadata, "", entity); - - if (entity.requiresPropertyPopulation()) { - ConvertingPropertyAccessor 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 metadata) { + public RowDocument toRowDocument(Class type, Readable row, + @Nullable Iterable metadata) { RowDocument document = new RowDocument(); RelationalPersistentEntity persistentEntity = getMappingContext().getPersistentEntity(type); @@ -155,26 +121,28 @@ public RowDocument toRowDocument(Class type, Readable row, Iterable metadata, + private static void captureRowValues(Readable row, @Nullable Iterable 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 @@ private static void captureRowValues(Readable row, Iterable 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 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) 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) target, value.toString()); - } - - return getConversionService().convert(value, target); - } - - @SuppressWarnings("unchecked") - private 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 createInstance(Row row, @Nullable RowMetadata rowMetadata, String prefix, - RelationalPersistentEntity entity) { - - InstanceCreatorMetadata persistenceConstructor = entity.getInstanceCreatorMetadata(); - ParameterValueProvider 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 @@ private static Collection asCollection(Object source) { return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source); } - enum NoOpParameterValueProvider implements ParameterValueProvider { - - INSTANCE; - - @Override - public T getParameterValue( - org.springframework.data.mapping.Parameter parameter) { - return null; - } - } - - private class RowParameterValueProvider implements ParameterValueProvider { - - 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 getParameterValue( - org.springframework.data.mapping.Parameter parameter) { - - RelationalPersistentProperty property = this.entity.getRequiredPersistentProperty(parameter.getName()); - Object value = readFrom(this.resultSet, this.metadata, property, this.prefix); - - if (value == null) { - return null; - } - - Class 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); - } - } - } } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/R2dbcConverter.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/R2dbcConverter.java index a774641bbe8..3c858a6d33f 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/R2dbcConverter.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/R2dbcConverter.java @@ -25,11 +25,9 @@ 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 @@ public interface R2dbcConverter extends EntityReader, EntityWriter, RelationalConverter { - /** - * Returns the underlying {@link MappingContext} used by the converter. - * - * @return never {@literal null} - */ - MappingContext, ? extends RelationalPersistentProperty> getMappingContext(); - /** * Returns the underlying {@link ConversionService} used by the converter. * diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AbstractRelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AbstractRelationalConverter.java new file mode 100644 index 00000000000..b1e302af464 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AbstractRelationalConverter.java @@ -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; + } + +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java index 05ffb29ab18..67f54864b11 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java @@ -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 @@ * @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 BasicRelationalConverter(RelationalMappingContext context) { * @param conversions must not be {@literal null}. */ public BasicRelationalConverter(RelationalMappingContext context, CustomConversions conversions) { - this(context, conversions, new DefaultConversionService(), new EntityInstantiators()); - } - - @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 PersistentPropertyPathAccessor getPropertyAccessor(PersistentEntity persistentEntity, - T instance) { - - PersistentPropertyPathAccessor accessor = persistentEntity.getPropertyPathAccessor(instance); - return new ConvertingPropertyAccessor<>(accessor, conversionService); - } - - @Override - public EntityProjection introspectProjection(Class resultType, Class entityType) { - throw new UnsupportedOperationException(); - } - - @Override - public ProjectionFactory getProjectionFactory() { - throw new UnsupportedOperationException(); - } - - @Override - public R project(EntityProjection descriptor, RowDocument document) { - throw new UnsupportedOperationException(); - } - - @Override - public R read(Class type, RowDocument source) { - throw new UnsupportedOperationException(); + super(context, conversions); } - @Override - public T createInstance(PersistentEntity entity, - Function, 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> 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 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> 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) target, value.toString()); - } - - return conversionService.convert(value, TypeDescriptor.forObject(value), createTypeDescriptor(type)); - } - - private static TypeDescriptor createTypeDescriptor(TypeInformation type) { - - List> 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

- * @author Mark Paluch - */ - class ConvertingParameterValueProvider

> implements ParameterValueProvider

{ - - private final Function, Object> delegate; - - ConvertingParameterValueProvider(Function, Object> delegate) { - - Assert.notNull(delegate, "Delegate must not be null"); - - this.delegate = delegate; - } - - @Override - @SuppressWarnings("unchecked") - public T getParameterValue(Parameter parameter) { - return (T) readValue(delegate.apply(parameter), parameter.getType()); - } - } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java index 30aaac0ce9a..30a317e07aa 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java @@ -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.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.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 @@ * {@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 void setApplicationContext(ApplicationContext applicationContext) throws this.projectionFactory.setBeanClassLoader(applicationContext.getClassLoader()); } - @Override - public ProjectionFactory getProjectionFactory() { - return this.projectionFactory; - } - /** * Creates a new {@link ConversionContext}. * @@ -142,6 +150,14 @@ protected ConversionContext getConversionContext(ObjectPath path) { this::readMap, this::getPotentiallyConvertedSimpleRead); } + @Override + public PersistentPropertyPathAccessor getPropertyAccessor(PersistentEntity persistentEntity, + T instance) { + + PersistentPropertyPathAccessor accessor = persistentEntity.getPropertyPathAccessor(instance); + return new ConvertingPropertyAccessor<>(accessor, getConversionService()); + } + @Override public EntityProjection introspectProjection(Class resultType, Class entityType) { @@ -315,7 +331,7 @@ protected S readAggregate(ConversionContext context, RowDocum } if (RowDocument.class.isAssignableFrom(rawType)) { - return (S) documentAccessor; + return (S) documentAccessor.document(); } if (typeHint.isMap()) { @@ -445,6 +461,14 @@ private S read(ConversionContext context, RelationalPersistentEntity enti return instance; } + @Override + public T createInstance(PersistentEntity entity, + Function, Object> parameterValueProvider) { + + return getEntityInstantiators().getInstantiatorFor(entity) // + .createInstance(entity, new ConvertingParameterValueProvider<>(parameterValueProvider)); + } + private ParameterValueProvider getParameterProvider(ConversionContext context, RelationalPersistentEntity entity, RowDocumentAccessor source, SpELExpressionEvaluator evaluator) { @@ -568,6 +592,165 @@ private boolean shouldReadEmbeddable(ConversionContext context, RelationalPersis 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> 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) target, value.toString()); + } + + return getConversionService().convert(value, TypeDescriptor.forObject(value), createTypeDescriptor(type)); + } + + private static TypeDescriptor createTypeDescriptor(TypeInformation type) { + + List> 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> 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 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 isConstructorArgument(PersistentEntity entity) { return entity::isCreatorArgument; } @@ -805,13 +988,16 @@ public T getParameterValue(Parameter parame } } - // 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 { /** * 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 @@ protected interface RelationalPropertyValueProvider extends PropertyValueProvide /** * Contextualize this property value provider. * - * @param context + * @param context the context to use. * @return */ RelationalPropertyValueProvider withContext(ConversionContext context); @@ -834,15 +1020,21 @@ protected interface AggregatePathValueProvider extends RelationalPropertyValuePr /** * 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 @@ protected interface AggregatePathValueProvider extends RelationalPropertyValuePr * @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 @@ protected record DocumentValueProvider(ConversionContext context, RowDocumentAcc * @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 DocumentValueProvider withContext(ConversionContext context) { 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

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

> implements ParameterValueProvider

{ + + private final Function, Object> delegate; + + ConvertingParameterValueProvider(Function, Object> delegate) { + + Assert.notNull(delegate, "Delegate must not be null"); + + this.delegate = delegate; + } + + @Override + @SuppressWarnings("unchecked") + public T getParameterValue(Parameter parameter) { + return (T) readValue(delegate.apply(parameter), parameter.getType()); + } } /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java index c78d9ea61e3..9a15d6864f0 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java @@ -27,7 +27,6 @@ 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,6 +42,21 @@ */ public interface RelationalConverter { + /** + * Returns the underlying {@link ConversionService} used by the converter. + * + * @return never {@literal null}. + */ + ConversionService getConversionService(); + + /** + * Return the underlying {@link EntityInstantiators}. + * + * @return + * @since 2.3 + */ + EntityInstantiators getEntityInstantiators(); + /** * Returns the underlying {@link MappingContext} used by the converter. * @@ -51,19 +65,30 @@ public interface RelationalConverter { MappingContext, ? extends RelationalPersistentProperty> getMappingContext(); /** - * Returns the underlying {@link ConversionService} used by the converter. + * Create a new instance of {@link PersistentEntity} given {@link ParameterValueProvider} to obtain constructor + * properties. * - * @return never {@literal null}. + * @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 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. */ - ConversionService getConversionService(); + @Deprecated(since = "3.2") + default T createInstance(PersistentEntity entity, + Function, Object> parameterValueProvider) { + throw new UnsupportedOperationException("Not supported anymore. Use read(…) instead."); + } /** - * Returns the {@link ProjectionFactory} for this converter. + * Return a {@link PersistentPropertyAccessor} to access property values of the {@code instance}. * - * @return will never be {@literal null}. - * @since 3.2 + * @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}. */ - ProjectionFactory getProjectionFactory(); + PersistentPropertyPathAccessor getPropertyAccessor(PersistentEntity 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 read(Class 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 the type of entity to create. - * @return the instantiated entity. Guaranteed to be not {@code null}. - */ - T createInstance(PersistentEntity entity, - Function, 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}. - */ - PersistentPropertyPathAccessor getPropertyAccessor(PersistentEntity persistentEntity, T instance); - /** * Read a relational value into the desired {@link TypeInformation destination type}. * @@ -143,11 +146,5 @@ T createInstance(PersistentEntity entity, @Nullable Object writeValue(@Nullable Object value, TypeInformation type); - /** - * Return the underlying {@link EntityInstantiators}. - * - * @return - * @since 2.3 - */ - EntityInstantiators getEntityInstantiators(); + }