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 c57125233c..0dd05e4cc2 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 @@ -17,7 +17,6 @@ import java.sql.Array; import java.sql.JDBCType; -import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLType; import java.util.Iterator; @@ -35,18 +34,10 @@ 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; @@ -83,8 +74,6 @@ public class BasicJdbcConverter extends MappingRelationalConverter implements Jd 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; @@ -104,7 +93,27 @@ public BasicJdbcConverter(RelationalMappingContext context, RelationResolver rel Assert.notNull(relationResolver, "RelationResolver must not be null"); this.typeFactory = JdbcTypeFactory.unsupported(); - this.identifierProcessing = IdentifierProcessing.ANSI; + this.relationResolver = relationResolver; + this.spELContext = new SpELContext(ResultSetAccessorPropertyAccessor.INSTANCE); + } + + /** + * Creates a new {@link BasicJdbcConverter} given {@link MappingContext}. + * + * @param context must not be {@literal null}. + * @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}. + * @param typeFactory must not be {@literal null} + * @since 3.2 + */ + public BasicJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver, + CustomConversions conversions, JdbcTypeFactory typeFactory) { + + super(context, conversions); + + Assert.notNull(typeFactory, "JdbcTypeFactory must not be null"); + Assert.notNull(relationResolver, "RelationResolver must not be null"); + + this.typeFactory = typeFactory; this.relationResolver = relationResolver; this.spELContext = new SpELContext(ResultSetAccessorPropertyAccessor.INSTANCE); } @@ -117,7 +126,9 @@ public BasicJdbcConverter(RelationalMappingContext context, RelationResolver rel * @param typeFactory must not be {@literal null} * @param identifierProcessing must not be {@literal null} * @since 2.0 + * @deprecated since 3.2 */ + @Deprecated(since = "3.2") public BasicJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver, CustomConversions conversions, JdbcTypeFactory typeFactory, IdentifierProcessing identifierProcessing) { @@ -125,10 +136,8 @@ public BasicJdbcConverter(RelationalMappingContext context, RelationResolver rel 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); } @@ -299,28 +308,6 @@ private JdbcValue tryToConvertToJdbcValue(@Nullable Object value) { 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) { @@ -413,16 +400,19 @@ public T getPropertyValue(RelationalPersistentProperty property) { if (property.isCollectionLike() || property.isMap()) { - Identifier identifier1 = this.identifier; + Identifier identifierToUse = 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); + identifierToUse = Identifier.of(aggregatePath.getTableInfo().reverseColumnInfo().name(), id, + Object.class); } } - Iterable allByPath = relationResolver.findAllByPath(identifier1, + Iterable allByPath = relationResolver.findAllByPath(identifierToUse, aggregatePath.getRequiredPersistentPropertyPath()); if (property.isCollectionLike()) { @@ -480,7 +470,6 @@ public RelationalPropertyValueProvider withContext(ConversionContext context) { : new ResolvingRelationalPropertyValueProvider(delegate.withContext(context), accessor, (ResolvingConversionContext) context, identifier); } - } /** @@ -573,261 +562,4 @@ static Object[] requireObjectArray(Object source) { 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; - } - } - } 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 1568374623..b2e960b0ab 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 f7c48304ce..6213c4ed96 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 2ec77afe36..c929605b64 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 = EntityRowMapper.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/RowDocumentResultSetExtractor.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentResultSetExtractor.java index 2f558c71ac..61644bbe97 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 @@ -51,6 +51,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}. */