Migrate JDBC to use RowDocument for reading aggregates.
Original pull request #1618 See #1554
This commit is contained in:
committed by
Jens Schauder
parent
665ae6b5a8
commit
4e3120ee0e
@@ -20,8 +20,10 @@ 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;
|
||||
@@ -44,13 +46,17 @@ 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;
|
||||
@@ -83,18 +89,15 @@ public class BasicJdbcConverter extends MappingRelationalConverter implements Jd
|
||||
private SpELContext spELContext;
|
||||
|
||||
/**
|
||||
* Creates a new {@link BasicJdbcConverter} given {@link MappingContext} and a
|
||||
* {@link JdbcTypeFactory#unsupported() no-op type factory} throwing {@link UnsupportedOperationException} on type
|
||||
* creation. Use
|
||||
* Creates a new {@link BasicJdbcConverter} given {@link MappingContext} and a {@link JdbcTypeFactory#unsupported()
|
||||
* no-op type factory} throwing {@link UnsupportedOperationException} on type creation. Use
|
||||
* {@link #BasicJdbcConverter(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 BasicJdbcConverter(
|
||||
RelationalMappingContext context,
|
||||
RelationResolver relationResolver) {
|
||||
public BasicJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver) {
|
||||
|
||||
super(context, new JdbcCustomConversions());
|
||||
|
||||
@@ -115,10 +118,8 @@ public class BasicJdbcConverter extends MappingRelationalConverter implements Jd
|
||||
* @param identifierProcessing must not be {@literal null}
|
||||
* @since 2.0
|
||||
*/
|
||||
public BasicJdbcConverter(
|
||||
RelationalMappingContext context,
|
||||
RelationResolver relationResolver, CustomConversions conversions, JdbcTypeFactory typeFactory,
|
||||
IdentifierProcessing identifierProcessing) {
|
||||
public BasicJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver,
|
||||
CustomConversions conversions, JdbcTypeFactory typeFactory, IdentifierProcessing identifierProcessing) {
|
||||
|
||||
super(context, conversions);
|
||||
|
||||
@@ -300,16 +301,241 @@ public class BasicJdbcConverter extends MappingRelationalConverter implements Jd
|
||||
|
||||
@Override
|
||||
public <T> T mapRow(RelationalPersistentEntity<T> entity, ResultSet resultSet, Object key) {
|
||||
return new ReadingContext<T>(getMappingContext().getAggregatePath( entity),
|
||||
new ResultSetAccessor(resultSet), Identifier.empty(), key).mapRow();
|
||||
return new ReadingContext<T>(getMappingContext().getAggregatePath(entity), new ResultSetAccessor(resultSet),
|
||||
Identifier.empty(), key).mapRow();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> T mapRow(AggregatePath path, ResultSet resultSet, Identifier identifier, Object key) {
|
||||
return new ReadingContext<T>(path, new ResultSetAccessor(resultSet), identifier, key).mapRow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R projectAndResolve(EntityProjection<R, ?> projection, RowDocument document) {
|
||||
|
||||
RelationalPersistentEntity<?> entity = getMappingContext()
|
||||
.getRequiredPersistentEntity(projection.getActualDomainType());
|
||||
ResolvingConversionContext context = new ResolvingConversionContext(newProjectingConversionContext(projection),
|
||||
getMappingContext().getAggregatePath(entity), Identifier.empty());
|
||||
|
||||
return doReadProjection(context, document, projection);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <R> R readAndResolve(Class<R> type, RowDocument source, Identifier identifier) {
|
||||
|
||||
RelationalPersistentEntity<R> entity = (RelationalPersistentEntity<R>) getMappingContext()
|
||||
.getRequiredPersistentEntity(type);
|
||||
AggregatePath path = getMappingContext().getAggregatePath(entity);
|
||||
Identifier identifierToUse = ResolvingRelationalPropertyValueProvider.potentiallyAppendIdentifier(identifier,
|
||||
entity, it -> source.get(it.getColumnName().getReference()));
|
||||
ResolvingConversionContext context = new ResolvingConversionContext(getConversionContext(ObjectPath.ROOT), path,
|
||||
identifierToUse);
|
||||
|
||||
return readAggregate(context, source, entity.getTypeInformation());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RelationalPropertyValueProvider newValueProvider(RowDocumentAccessor documentAccessor,
|
||||
SpELExpressionEvaluator evaluator, ConversionContext context) {
|
||||
|
||||
if (context instanceof ResolvingConversionContext rcc) {
|
||||
|
||||
AggregatePathValueProvider delegate = (AggregatePathValueProvider) super.newValueProvider(documentAccessor,
|
||||
evaluator, context);
|
||||
|
||||
return new ResolvingRelationalPropertyValueProvider(delegate, documentAccessor, rcc, rcc.identifier());
|
||||
}
|
||||
|
||||
return super.newValueProvider(documentAccessor, evaluator, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link RelationalPropertyValueProvider} using a resolving context to lookup relations. This is highly
|
||||
* context-sensitive. Note that the identifier is held here because of a chicken and egg problem, while
|
||||
* {@link ResolvingConversionContext} hols the {@link AggregatePath}.
|
||||
*/
|
||||
class ResolvingRelationalPropertyValueProvider implements RelationalPropertyValueProvider {
|
||||
|
||||
private final AggregatePathValueProvider delegate;
|
||||
|
||||
private final RowDocumentAccessor accessor;
|
||||
|
||||
private final ResolvingConversionContext context;
|
||||
|
||||
private final Identifier identifier;
|
||||
|
||||
private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider delegate, RowDocumentAccessor accessor,
|
||||
ResolvingConversionContext context, Identifier identifier) {
|
||||
|
||||
AggregatePath path = context.aggregatePath();
|
||||
|
||||
this.delegate = delegate;
|
||||
this.accessor = accessor;
|
||||
this.context = context;
|
||||
this.identifier = path.isEntity()
|
||||
? potentiallyAppendIdentifier(identifier, path.getRequiredLeafEntity(), delegate::getPropertyValue)
|
||||
: identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditionally append the identifier if the entity has an identifier property.
|
||||
*/
|
||||
static Identifier potentiallyAppendIdentifier(Identifier base, RelationalPersistentEntity<?> entity,
|
||||
Function<RelationalPersistentProperty, Object> getter) {
|
||||
|
||||
if (entity.hasIdProperty()) {
|
||||
|
||||
RelationalPersistentProperty idProperty = entity.getRequiredIdProperty();
|
||||
Object propertyValue = getter.apply(idProperty);
|
||||
|
||||
if (propertyValue != null) {
|
||||
return base.withPart(idProperty.getColumnName(), propertyValue, idProperty.getType());
|
||||
}
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T getPropertyValue(RelationalPersistentProperty property) {
|
||||
|
||||
AggregatePath aggregatePath = this.context.aggregatePath();
|
||||
|
||||
if (getConversions().isSimpleType(property.getActualType())) {
|
||||
return (T) delegate.getValue(aggregatePath);
|
||||
}
|
||||
|
||||
if (property.isEntity()) {
|
||||
|
||||
if (property.isCollectionLike() || property.isMap()) {
|
||||
|
||||
Identifier identifier1 = this.identifier;
|
||||
if (property.getOwner().hasIdProperty()) {
|
||||
Object id = this.identifier.get(property.getOwner().getRequiredIdProperty().getColumnName());
|
||||
|
||||
if (id != null) {
|
||||
identifier1 = Identifier.of(aggregatePath.getTableInfo().reverseColumnInfo().name(), id, Object.class);
|
||||
}
|
||||
}
|
||||
|
||||
Iterable<Object> allByPath = relationResolver.findAllByPath(identifier1,
|
||||
aggregatePath.getRequiredPersistentPropertyPath());
|
||||
|
||||
if (property.isCollectionLike()) {
|
||||
return (T) allByPath;
|
||||
}
|
||||
|
||||
if (property.isMap()) {
|
||||
return (T) ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(allByPath);
|
||||
}
|
||||
|
||||
Iterator<Object> iterator = allByPath.iterator();
|
||||
if (iterator.hasNext()) {
|
||||
return (T) iterator.next();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return hasValue(property) ? (T) readAggregate(this.context, accessor, property.getTypeInformation()) : null;
|
||||
}
|
||||
|
||||
return (T) delegate.getValue(aggregatePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasValue(RelationalPersistentProperty property) {
|
||||
|
||||
if (property.isCollectionLike() || property.isMap()) {
|
||||
// attempt relation fetch
|
||||
return true;
|
||||
}
|
||||
|
||||
AggregatePath aggregatePath = context.aggregatePath();
|
||||
|
||||
if (property.isEntity()) {
|
||||
|
||||
RelationalPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(property);
|
||||
if (entity.hasIdProperty()) {
|
||||
|
||||
RelationalPersistentProperty referenceId = entity.getRequiredIdProperty();
|
||||
AggregatePath toUse = aggregatePath.append(referenceId);
|
||||
return delegate.hasValue(toUse);
|
||||
}
|
||||
|
||||
return delegate.hasValue(aggregatePath.getTableInfo().reverseColumnInfo().alias());
|
||||
}
|
||||
|
||||
return delegate.hasValue(aggregatePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RelationalPropertyValueProvider withContext(ConversionContext context) {
|
||||
|
||||
return context == this.context ? this
|
||||
: new ResolvingRelationalPropertyValueProvider(delegate.withContext(context), accessor,
|
||||
(ResolvingConversionContext) context, identifier);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker object to indicate that the property value provider should resolve relations.
|
||||
*
|
||||
* @param delegate
|
||||
* @param aggregatePath
|
||||
* @param identifier
|
||||
*/
|
||||
private record ResolvingConversionContext(ConversionContext delegate, AggregatePath aggregatePath,
|
||||
Identifier identifier) implements ConversionContext {
|
||||
|
||||
@Override
|
||||
public <S> S convert(Object source, TypeInformation<? extends S> typeHint) {
|
||||
return delegate.convert(source, typeHint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> S convert(Object source, TypeInformation<? extends S> typeHint, ConversionContext context) {
|
||||
return delegate.convert(source, typeHint, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResolvingConversionContext forProperty(String name) {
|
||||
RelationalPersistentProperty property = aggregatePath.getRequiredLeafEntity().getRequiredPersistentProperty(name);
|
||||
return forProperty(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResolvingConversionContext forProperty(RelationalPersistentProperty property) {
|
||||
ConversionContext nested = delegate.forProperty(property);
|
||||
return new ResolvingConversionContext(nested, aggregatePath.append(property), identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResolvingConversionContext withPath(ObjectPath currentPath) {
|
||||
return new ResolvingConversionContext(delegate.withPath(currentPath), aggregatePath, identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectPath getPath() {
|
||||
return delegate.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomConversions getCustomConversions() {
|
||||
return delegate.getCustomConversions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RelationalConverter getSourceConverter() {
|
||||
return delegate.getSourceConverter();
|
||||
}
|
||||
}
|
||||
|
||||
static Object[] requireObjectArray(Object source) {
|
||||
|
||||
Assert.isTrue(source.getClass().isArray(), "Source object is not an array");
|
||||
@@ -361,15 +587,14 @@ public class BasicJdbcConverter extends MappingRelationalConverter implements Jd
|
||||
private final ResultSetAccessor accessor;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ReadingContext(AggregatePath rootPath, ResultSetAccessor accessor, Identifier identifier,
|
||||
Object key) {
|
||||
private ReadingContext(AggregatePath rootPath, ResultSetAccessor accessor, Identifier identifier, Object key) {
|
||||
RelationalPersistentEntity<T> entity = (RelationalPersistentEntity<T>) rootPath.getLeafEntity();
|
||||
|
||||
Assert.notNull(entity, "The rootPath must point to an entity");
|
||||
|
||||
this.entity = entity;
|
||||
this.rootPath = rootPath;
|
||||
this.path = getMappingContext().getAggregatePath( this.entity);
|
||||
this.path = getMappingContext().getAggregatePath(this.entity);
|
||||
this.identifier = identifier;
|
||||
this.key = key;
|
||||
this.propertyValueProvider = new JdbcPropertyValueProvider(path, accessor);
|
||||
@@ -377,9 +602,8 @@ public class BasicJdbcConverter extends MappingRelationalConverter implements Jd
|
||||
this.accessor = accessor;
|
||||
}
|
||||
|
||||
private ReadingContext(RelationalPersistentEntity<T> entity, AggregatePath rootPath,
|
||||
AggregatePath path, Identifier identifier, Object key,
|
||||
JdbcPropertyValueProvider propertyValueProvider,
|
||||
private ReadingContext(RelationalPersistentEntity<T> entity, AggregatePath rootPath, AggregatePath path,
|
||||
Identifier identifier, Object key, JdbcPropertyValueProvider propertyValueProvider,
|
||||
JdbcBackReferencePropertyValueProvider backReferencePropertyValueProvider, ResultSetAccessor accessor) {
|
||||
|
||||
this.entity = entity;
|
||||
@@ -396,8 +620,8 @@ public class BasicJdbcConverter extends MappingRelationalConverter implements Jd
|
||||
|
||||
return new ReadingContext<>(
|
||||
(RelationalPersistentEntity<S>) getMappingContext().getRequiredPersistentEntity(property.getActualType()),
|
||||
rootPath.append(property), path.append(property), identifier, key,
|
||||
propertyValueProvider.extendBy(property), backReferencePropertyValueProvider.extendBy(property), accessor);
|
||||
rootPath.append(property), path.append(property), identifier, key, propertyValueProvider.extendBy(property),
|
||||
backReferencePropertyValueProvider.extendBy(property), accessor);
|
||||
}
|
||||
|
||||
T mapRow() {
|
||||
|
||||
@@ -15,12 +15,17 @@
|
||||
*/
|
||||
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;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Maps a {@link ResultSet} to an entity of type {@code T}, including entities referenced. This {@link RowMapper} might
|
||||
@@ -41,8 +46,6 @@ public class EntityRowMapper<T> implements RowMapper<T> {
|
||||
private final Identifier identifier;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @deprecated use {@link EntityRowMapper#EntityRowMapper(AggregatePath, JdbcConverter, Identifier)} instead
|
||||
*/
|
||||
@Deprecated(since = "3.2", forRemoval = true)
|
||||
@@ -73,11 +76,41 @@ public class EntityRowMapper<T> implements RowMapper<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public T mapRow(ResultSet resultSet, int rowNumber) {
|
||||
public T mapRow(ResultSet resultSet, int rowNumber) throws SQLException {
|
||||
|
||||
RowDocument document = 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ public final class Identifier {
|
||||
Assert.notNull(name, "Name must not be empty");
|
||||
Assert.notNull(targetType, "Target type must not be null");
|
||||
|
||||
// TODO: Is value allowed to be null? SingleIdentifierValue says so, but this type doesn't allows it and
|
||||
// SqlParametersFactory.lambda$forQueryByIdentifier$1 fails with a NPE.
|
||||
return new Identifier(Collections.singletonList(new SingleIdentifierValue(name, value, targetType)));
|
||||
}
|
||||
|
||||
@@ -173,6 +175,18 @@ public final class Identifier {
|
||||
return this.parts.size();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Object get(SqlIdentifier columnName) {
|
||||
|
||||
for (SingleIdentifierValue part : parts) {
|
||||
if (part.getName().equals(columnName)) {
|
||||
return part.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A single value of an Identifier consisting of the column name, the value and the target type which is to be used to
|
||||
* store the element in the database.
|
||||
@@ -274,8 +288,10 @@ public final class Identifier {
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
Identifier that = (Identifier) o;
|
||||
return Objects.equals(parts, that.parts);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
/*
|
||||
* Copyright 2019-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.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2019-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.ResultSet;
|
||||
import java.sql.SQLType;
|
||||
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcValue;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
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;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.data.relational.domain.RowDocument;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -73,7 +73,7 @@ public interface JdbcConverter extends RelationalConverter {
|
||||
* @deprecated use {@link #mapRow(AggregatePath, ResultSet, Identifier, Object)} instead.
|
||||
*/
|
||||
@Deprecated(since = "3.2", forRemoval = true)
|
||||
default <T> T mapRow(PersistentPropertyPathExtension path, ResultSet resultSet, Identifier identifier, Object key){
|
||||
default <T> T mapRow(PersistentPropertyPathExtension path, ResultSet resultSet, Identifier identifier, Object key) {
|
||||
return mapRow(path.getAggregatePath(), resultSet, identifier, key);
|
||||
};
|
||||
|
||||
@@ -89,6 +89,49 @@ public interface JdbcConverter extends RelationalConverter {
|
||||
*/
|
||||
<T> T mapRow(AggregatePath path, ResultSet resultSet, Identifier identifier, Object key);
|
||||
|
||||
/**
|
||||
* Apply a projection to {@link RowDocument} and return the projection return type {@code R}.
|
||||
* {@link EntityProjection#isProjection() Non-projecting} descriptors fall back to {@link #read(Class, RowDocument)
|
||||
* regular object materialization}.
|
||||
*
|
||||
* @param descriptor the projection descriptor, must not be {@literal null}.
|
||||
* @param document must not be {@literal null}.
|
||||
* @param <R>
|
||||
* @return a new instance of the projection return type {@code R}.
|
||||
* @since 3.2
|
||||
* @see #project(EntityProjection, RowDocument)
|
||||
*/
|
||||
<R> R projectAndResolve(EntityProjection<R, ?> descriptor, RowDocument document);
|
||||
|
||||
/**
|
||||
* Read a {@link RowDocument} into the requested {@link Class aggregate type} and resolve references by looking these
|
||||
* up from {@link RelationResolver}.
|
||||
*
|
||||
* @param type target aggregate type.
|
||||
* @param source source {@link RowDocument}.
|
||||
* @return the converted object.
|
||||
* @param <R> aggregate type.
|
||||
* @since 3.2
|
||||
* @see #read(Class, RowDocument)
|
||||
*/
|
||||
default <R> R readAndResolve(Class<R> type, RowDocument source) {
|
||||
return readAndResolve(type, source, Identifier.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a {@link RowDocument} into the requested {@link Class aggregate type} and resolve references by looking these
|
||||
* up from {@link RelationResolver}.
|
||||
*
|
||||
* @param type target aggregate type.
|
||||
* @param source source {@link RowDocument}.
|
||||
* @param identifier identifier chain.
|
||||
* @return the converted object.
|
||||
* @param <R> aggregate type.
|
||||
* @since 3.2
|
||||
* @see #read(Class, RowDocument)
|
||||
*/
|
||||
<R> R readAndResolve(Class<R> type, RowDocument source, Identifier identifier);
|
||||
|
||||
/**
|
||||
* The type to be used to store this property in the database. Multidimensional arrays are unwrapped to reflect a
|
||||
* top-level array type (e.g. {@code String[][]} returns {@code String[]}).
|
||||
@@ -110,4 +153,5 @@ public interface JdbcConverter extends RelationalConverter {
|
||||
|
||||
@Override
|
||||
RelationalMappingContext getMappingContext();
|
||||
|
||||
}
|
||||
|
||||
@@ -21,9 +21,8 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.relational.core.mapping.AggregatePath;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
import org.springframework.data.relational.core.sql.IdentifierProcessing;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.data.relational.domain.RowDocument;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
/**
|
||||
@@ -56,7 +55,14 @@ class MapEntityRowMapper<T> implements RowMapper<Map.Entry<Object, T>> {
|
||||
return new HashMap.SimpleEntry<>(key, mapEntity(rs, key));
|
||||
}
|
||||
|
||||
private T mapEntity(ResultSet resultSet, Object key) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1534,6 +1534,15 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
public int hashCode() {
|
||||
return Objects.hash(zeroValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(" [zeroValue='").append(zeroValue).append('\'');
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class NoIdListChain1 {
|
||||
@@ -1554,6 +1563,16 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
public int hashCode() {
|
||||
return Objects.hash(oneValue, chain0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(" [oneValue='").append(oneValue).append('\'');
|
||||
sb.append(", chain0=").append(chain0);
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class NoIdListChain2 {
|
||||
@@ -1574,6 +1593,16 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
public int hashCode() {
|
||||
return Objects.hash(twoValue, chain1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(" [twoValue='").append(twoValue).append('\'');
|
||||
sb.append(", chain1=").append(chain1);
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class NoIdListChain3 {
|
||||
@@ -1594,6 +1623,16 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
public int hashCode() {
|
||||
return Objects.hash(threeValue, chain2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(" [threeValue='").append(threeValue).append('\'');
|
||||
sb.append(", chain2=").append(chain2);
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class NoIdListChain4 {
|
||||
@@ -1616,6 +1655,18 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
public int hashCode() {
|
||||
return Objects.hash(four, fourValue, chain3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(" [four=").append(four);
|
||||
sb.append(", fourValue='").append(fourValue).append('\'');
|
||||
sb.append(", chain3=").append(chain3);
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1638,6 +1689,15 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
public int hashCode() {
|
||||
return Objects.hash(zeroValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(" [zeroValue='").append(zeroValue).append('\'');
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class NoIdMapChain1 {
|
||||
@@ -1658,6 +1718,16 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
public int hashCode() {
|
||||
return Objects.hash(oneValue, chain0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(" [oneValue='").append(oneValue).append('\'');
|
||||
sb.append(", chain0=").append(chain0);
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class NoIdMapChain2 {
|
||||
@@ -1678,6 +1748,16 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
public int hashCode() {
|
||||
return Objects.hash(twoValue, chain1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(" [twoValue='").append(twoValue).append('\'');
|
||||
sb.append(", chain1=").append(chain1);
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class NoIdMapChain3 {
|
||||
@@ -1698,6 +1778,16 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
public int hashCode() {
|
||||
return Objects.hash(threeValue, chain2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(" [threeValue='").append(threeValue).append('\'');
|
||||
sb.append(", chain2=").append(chain2);
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class NoIdMapChain4 {
|
||||
@@ -1720,6 +1810,17 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
public int hashCode() {
|
||||
return Objects.hash(four, fourValue, chain3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(" [four=").append(four);
|
||||
sb.append(", fourValue='").append(fourValue).append('\'');
|
||||
sb.append(", chain3=").append(chain3);
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
||||
@@ -302,7 +302,7 @@ public class BasicRelationalConverter implements RelationalConverter {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (Enum.class.isAssignableFrom(target)) {
|
||||
if (Enum.class.isAssignableFrom(target) && value instanceof CharSequence) {
|
||||
return Enum.valueOf((Class<Enum>) target, value.toString());
|
||||
}
|
||||
|
||||
|
||||
@@ -49,12 +49,14 @@ import org.springframework.data.projection.EntityProjectionIntrospector;
|
||||
import org.springframework.data.projection.EntityProjectionIntrospector.ProjectionPredicate;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.relational.core.mapping.AggregatePath;
|
||||
import org.springframework.data.relational.core.mapping.Embedded;
|
||||
import org.springframework.data.relational.core.mapping.Embedded.OnEmpty;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyTranslator;
|
||||
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.SqlIdentifier;
|
||||
import org.springframework.data.relational.domain.RowDocument;
|
||||
import org.springframework.data.util.Predicates;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
@@ -161,14 +163,17 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
return (R) read(typeToRead, document);
|
||||
}
|
||||
|
||||
ProjectingConversionContext context = new ProjectingConversionContext(this, getConversions(), ObjectPath.ROOT,
|
||||
this::readCollectionOrArray, this::readMap, this::getPotentiallyConvertedSimpleRead, projection);
|
||||
|
||||
ProjectingConversionContext context = newProjectingConversionContext(projection);
|
||||
return doReadProjection(context, document, projection);
|
||||
}
|
||||
|
||||
protected <R> ProjectingConversionContext newProjectingConversionContext(EntityProjection<R, ?> projection) {
|
||||
return new ProjectingConversionContext(this, getConversions(), ObjectPath.ROOT, this::readCollectionOrArray,
|
||||
this::readMap, this::getPotentiallyConvertedSimpleRead, projection);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <R> R doReadProjection(ConversionContext context, RowDocument document, EntityProjection<R, ?> projection) {
|
||||
protected <R> R doReadProjection(ConversionContext context, RowDocument document, EntityProjection<R, ?> projection) {
|
||||
|
||||
RelationalPersistentEntity<?> entity = getMappingContext()
|
||||
.getRequiredPersistentEntity(projection.getActualDomainType());
|
||||
@@ -186,8 +191,7 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
|
||||
PersistentPropertyAccessor<?> convertingAccessor = PropertyTranslatingPropertyAccessor
|
||||
.create(new ConvertingPropertyAccessor<>(accessor, getConversionService()), propertyTranslator);
|
||||
RelationalPropertyValueProvider valueProvider = new RelationalPropertyValueProvider(context, documentAccessor,
|
||||
evaluator, spELContext);
|
||||
RelationalPropertyValueProvider valueProvider = newValueProvider(documentAccessor, evaluator, context);
|
||||
|
||||
readProperties(context, entity, convertingAccessor, documentAccessor, valueProvider, Predicates.isTrue());
|
||||
return (R) projectionFactory.createProjection(mappedType.getType(), accessor.getBean());
|
||||
@@ -224,8 +228,7 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
|
||||
PersistentPropertyAccessor<?> convertingAccessor = new ConvertingPropertyAccessor<>(accessor,
|
||||
getConversionService());
|
||||
RelationalPropertyValueProvider valueProvider = new RelationalPropertyValueProvider(context, documentAccessor,
|
||||
evaluator, spELContext);
|
||||
RelationalPropertyValueProvider valueProvider = newValueProvider(documentAccessor, evaluator, context);
|
||||
|
||||
readProperties(context, mappedEntity, convertingAccessor, documentAccessor, valueProvider, Predicates.isTrue());
|
||||
|
||||
@@ -290,19 +293,33 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <S extends Object> S readAggregate(ConversionContext context, RowDocument document,
|
||||
TypeInformation<? extends S> typeHint) {
|
||||
return readAggregate(context, new RowDocumentAccessor(document), typeHint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversion method to materialize an object from a {@link RowDocument document}. Can be overridden by subclasses.
|
||||
*
|
||||
* @param context must not be {@literal null}
|
||||
* @param documentAccessor must not be {@literal null}
|
||||
* @param typeHint the {@link TypeInformation} to be used to unmarshall this {@link RowDocument}.
|
||||
* @return the converted object, will never be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <S extends Object> S readAggregate(ConversionContext context, RowDocumentAccessor documentAccessor,
|
||||
TypeInformation<? extends S> typeHint) {
|
||||
|
||||
Class<? extends S> rawType = typeHint.getType();
|
||||
|
||||
if (getConversions().hasCustomReadTarget(document.getClass(), rawType)) {
|
||||
return doConvert(document, rawType, typeHint.getType());
|
||||
if (getConversions().hasCustomReadTarget(documentAccessor.getClass(), rawType)) {
|
||||
return doConvert(documentAccessor, rawType, typeHint.getType());
|
||||
}
|
||||
|
||||
if (RowDocument.class.isAssignableFrom(rawType)) {
|
||||
return (S) document;
|
||||
return (S) documentAccessor;
|
||||
}
|
||||
|
||||
if (typeHint.isMap()) {
|
||||
return context.convert(document, typeHint);
|
||||
return context.convert(documentAccessor, typeHint);
|
||||
}
|
||||
|
||||
RelationalPersistentEntity<?> entity = getMappingContext().getPersistentEntity(rawType);
|
||||
@@ -310,10 +327,10 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
if (entity == null) {
|
||||
throw new MappingException(
|
||||
String.format("Expected to read Document %s into type %s but didn't find a PersistentEntity for the latter",
|
||||
document, rawType));
|
||||
documentAccessor, rawType));
|
||||
}
|
||||
|
||||
return read(context, (RelationalPersistentEntity<S>) entity, document);
|
||||
return read(context, (RelationalPersistentEntity<S>) entity, documentAccessor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,7 +381,6 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
* @param targetType the {@link Map} {@link TypeInformation} to be used to unmarshall this {@link RowDocument}.
|
||||
* @return the converted {@link Collection} or array, will never be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object readCollectionOrArray(ConversionContext context, Collection<?> source,
|
||||
TypeInformation<?> targetType) {
|
||||
|
||||
@@ -408,10 +424,10 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
return getConversionService().convert(value, fallback);
|
||||
}
|
||||
|
||||
private <S> S read(ConversionContext context, RelationalPersistentEntity<S> entity, RowDocument document) {
|
||||
private <S> S read(ConversionContext context, RelationalPersistentEntity<S> entity,
|
||||
RowDocumentAccessor documentAccessor) {
|
||||
|
||||
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(document, spELContext);
|
||||
RowDocumentAccessor documentAccessor = new RowDocumentAccessor(document);
|
||||
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(documentAccessor.getDocument(), spELContext);
|
||||
|
||||
InstanceCreatorMetadata<RelationalPersistentProperty> instanceCreatorMetadata = entity.getInstanceCreatorMetadata();
|
||||
|
||||
@@ -432,12 +448,37 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
private ParameterValueProvider<RelationalPersistentProperty> getParameterProvider(ConversionContext context,
|
||||
RelationalPersistentEntity<?> entity, RowDocumentAccessor source, SpELExpressionEvaluator evaluator) {
|
||||
|
||||
RelationalPropertyValueProvider provider = new RelationalPropertyValueProvider(context, source, evaluator,
|
||||
spELContext);
|
||||
// Ensure that ConversionContext is contextualized to the current property.
|
||||
RelationalPropertyValueProvider contextualizing = new RelationalPropertyValueProvider() {
|
||||
@Override
|
||||
public boolean hasValue(RelationalPersistentProperty property) {
|
||||
return withContext(context.forProperty(property)).hasValue(property);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T getPropertyValue(RelationalPersistentProperty property) {
|
||||
|
||||
ConversionContext propertyContext = context.forProperty(property);
|
||||
RelationalPropertyValueProvider provider = withContext(propertyContext);
|
||||
|
||||
if (property.isEmbedded()) {
|
||||
return (T) readEmbedded(propertyContext, provider, source, property,
|
||||
getMappingContext().getRequiredPersistentEntity(property));
|
||||
}
|
||||
|
||||
return provider.getPropertyValue(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RelationalPropertyValueProvider withContext(ConversionContext context) {
|
||||
return newValueProvider(source, evaluator, context);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Add support for enclosing object (non-static inner classes)
|
||||
PersistentEntityParameterValueProvider<RelationalPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
|
||||
entity, provider, context.getPath().getCurrentObject());
|
||||
entity, contextualizing, context.getPath().getCurrentObject());
|
||||
|
||||
return new ConverterAwareSpELExpressionParameterValueProvider(context, evaluator, getConversionService(),
|
||||
parameterProvider);
|
||||
@@ -453,8 +494,7 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
ObjectPath currentPath = context.getPath().push(accessor.getBean(), entity);
|
||||
ConversionContext contextToUse = context.withPath(currentPath);
|
||||
|
||||
RelationalPropertyValueProvider valueProvider = new RelationalPropertyValueProvider(contextToUse, documentAccessor,
|
||||
evaluator, spELContext);
|
||||
RelationalPropertyValueProvider valueProvider = newValueProvider(documentAccessor, evaluator, contextToUse);
|
||||
|
||||
Predicate<RelationalPersistentProperty> propertyFilter = isConstructorArgument(entity).negate();
|
||||
readProperties(contextToUse, entity, accessor, documentAccessor, valueProvider, propertyFilter);
|
||||
@@ -462,56 +502,72 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
return accessor.getBean();
|
||||
}
|
||||
|
||||
protected RelationalPropertyValueProvider newValueProvider(RowDocumentAccessor documentAccessor,
|
||||
SpELExpressionEvaluator evaluator, ConversionContext context) {
|
||||
return new DocumentValueProvider(context, documentAccessor, evaluator, spELContext);
|
||||
}
|
||||
|
||||
private void readProperties(ConversionContext context, RelationalPersistentEntity<?> entity,
|
||||
PersistentPropertyAccessor<?> accessor, RowDocumentAccessor documentAccessor,
|
||||
RelationalPropertyValueProvider valueProvider, Predicate<RelationalPersistentProperty> propertyFilter) {
|
||||
|
||||
for (RelationalPersistentProperty prop : entity) {
|
||||
for (RelationalPersistentProperty property : entity) {
|
||||
|
||||
if (!propertyFilter.test(prop)) {
|
||||
if (!propertyFilter.test(property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ConversionContext propertyContext = context.forProperty(prop);
|
||||
ConversionContext propertyContext = context.forProperty(property);
|
||||
RelationalPropertyValueProvider valueProviderToUse = valueProvider.withContext(propertyContext);
|
||||
|
||||
if (prop.isAssociation()) {
|
||||
|
||||
// TODO: Read AggregateReference
|
||||
if (property.isEmbedded()) {
|
||||
accessor.setProperty(property, readEmbedded(propertyContext, valueProviderToUse, documentAccessor, property,
|
||||
getMappingContext().getRequiredPersistentEntity(property)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prop.isEmbedded()) {
|
||||
accessor.setProperty(prop, readEmbedded(propertyContext, documentAccessor, prop,
|
||||
getMappingContext().getRequiredPersistentEntity(prop)));
|
||||
if (!valueProviderToUse.hasValue(property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!documentAccessor.hasValue(prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
accessor.setProperty(prop, valueProviderToUse.getPropertyValue(prop));
|
||||
accessor.setProperty(property, valueProviderToUse.getPropertyValue(property));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object readEmbedded(ConversionContext context, RowDocumentAccessor documentAccessor,
|
||||
RelationalPersistentProperty prop, RelationalPersistentEntity<?> unwrappedEntity) {
|
||||
private Object readEmbedded(ConversionContext conversionContext, RelationalPropertyValueProvider provider,
|
||||
RowDocumentAccessor source, RelationalPersistentProperty property,
|
||||
RelationalPersistentEntity<?> persistentEntity) {
|
||||
|
||||
if (prop.findAnnotation(Embedded.class).onEmpty().equals(OnEmpty.USE_EMPTY)) {
|
||||
return read(context, unwrappedEntity, documentAccessor.getDocument());
|
||||
}
|
||||
|
||||
for (RelationalPersistentProperty persistentProperty : unwrappedEntity) {
|
||||
if (documentAccessor.hasValue(persistentProperty)) {
|
||||
return read(context, unwrappedEntity, documentAccessor.getDocument());
|
||||
}
|
||||
if (shouldReadEmbeddable(conversionContext, property, persistentEntity, provider)) {
|
||||
return read(conversionContext, persistentEntity, source);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean shouldReadEmbeddable(ConversionContext context, RelationalPersistentProperty property,
|
||||
RelationalPersistentEntity<?> unwrappedEntity, RelationalPropertyValueProvider propertyValueProvider) {
|
||||
|
||||
OnEmpty onEmpty = property.getRequiredAnnotation(Embedded.class).onEmpty();
|
||||
|
||||
if (onEmpty.equals(OnEmpty.USE_EMPTY)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (RelationalPersistentProperty persistentProperty : unwrappedEntity) {
|
||||
|
||||
RelationalPropertyValueProvider contextual = propertyValueProvider
|
||||
.withContext(context.forProperty(persistentProperty));
|
||||
|
||||
if (contextual.hasValue(persistentProperty)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static Predicate<RelationalPersistentProperty> isConstructorArgument(PersistentEntity<?, ?> entity) {
|
||||
return entity::isCreatorArgument;
|
||||
}
|
||||
@@ -532,7 +588,7 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
final ContainerValueConverter<Map<?, ?>> mapConverter;
|
||||
final ValueConverter<Object> elementConverter;
|
||||
|
||||
DefaultConversionContext(RelationalConverter sourceConverter,
|
||||
protected DefaultConversionContext(RelationalConverter sourceConverter,
|
||||
org.springframework.data.convert.CustomConversions customConversions, ObjectPath objectPath,
|
||||
ContainerValueConverter<RowDocument> documentConverter,
|
||||
ContainerValueConverter<Collection<?>> collectionConverter, ContainerValueConverter<Map<?, ?>> mapConverter,
|
||||
@@ -616,7 +672,7 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
interface ValueConverter<T> {
|
||||
public interface ValueConverter<T> {
|
||||
|
||||
Object convert(T source, TypeInformation<?> typeHint);
|
||||
|
||||
@@ -628,7 +684,7 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
interface ContainerValueConverter<T> {
|
||||
public interface ContainerValueConverter<T> {
|
||||
|
||||
Object convert(ConversionContext context, T source, TypeInformation<?> typeHint);
|
||||
|
||||
@@ -639,11 +695,11 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
/**
|
||||
* @since 3.4.3
|
||||
*/
|
||||
class ProjectingConversionContext extends DefaultConversionContext {
|
||||
protected class ProjectingConversionContext extends DefaultConversionContext {
|
||||
|
||||
private final EntityProjection<?, ?> returnedTypeDescriptor;
|
||||
|
||||
ProjectingConversionContext(RelationalConverter sourceConverter, CustomConversions customConversions,
|
||||
protected ProjectingConversionContext(RelationalConverter sourceConverter, CustomConversions customConversions,
|
||||
ObjectPath path, ContainerValueConverter<Collection<?>> collectionConverter,
|
||||
ContainerValueConverter<Map<?, ?>> mapConverter, ValueConverter<Object> elementConverter,
|
||||
EntityProjection<?, ?> projection) {
|
||||
@@ -749,6 +805,61 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Docs
|
||||
protected interface RelationalPropertyValueProvider extends PropertyValueProvider<RelationalPersistentProperty> {
|
||||
|
||||
/**
|
||||
* Determine whether there is a value for the given {@link RelationalPersistentProperty}.
|
||||
*
|
||||
* @param property
|
||||
* @return
|
||||
*/
|
||||
boolean hasValue(RelationalPersistentProperty property);
|
||||
|
||||
/**
|
||||
* Contextualize this property value provider.
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
RelationalPropertyValueProvider withContext(ConversionContext context);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link RelationalPropertyValueProvider} extension to obtain values for {@link AggregatePath}s.
|
||||
*/
|
||||
protected interface AggregatePathValueProvider extends RelationalPropertyValueProvider {
|
||||
|
||||
/**
|
||||
* Determine whether there is a value for the given {@link AggregatePath}.
|
||||
*
|
||||
* @param path
|
||||
* @return
|
||||
*/
|
||||
boolean hasValue(AggregatePath path);
|
||||
|
||||
boolean hasValue(SqlIdentifier identifier);
|
||||
|
||||
/**
|
||||
* Returns a value for the given {@link AggregatePath}.
|
||||
*
|
||||
* @param path will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
Object getValue(AggregatePath path);
|
||||
|
||||
/**
|
||||
* Contextualize this property value provider.
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
AggregatePathValueProvider withContext(ConversionContext context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PropertyValueProvider} to evaluate a SpEL expression if present on the property or simply accesses the field
|
||||
* of the configured source {@link RowDocument}.
|
||||
@@ -757,9 +868,9 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
record RelationalPropertyValueProvider(ConversionContext context, RowDocumentAccessor accessor,
|
||||
protected record DocumentValueProvider(ConversionContext context, RowDocumentAccessor accessor,
|
||||
SpELExpressionEvaluator evaluator,
|
||||
SpELContext spELContext) implements PropertyValueProvider<RelationalPersistentProperty> {
|
||||
SpELContext spELContext) implements RelationalPropertyValueProvider, AggregatePathValueProvider {
|
||||
|
||||
/**
|
||||
* Creates a new {@link RelationalPropertyValueProvider} for the given source and {@link SpELExpressionEvaluator}.
|
||||
@@ -768,13 +879,14 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
* @param accessor must not be {@literal null}.
|
||||
* @param evaluator must not be {@literal null}.
|
||||
*/
|
||||
RelationalPropertyValueProvider {
|
||||
protected DocumentValueProvider {
|
||||
|
||||
Assert.notNull(context, "ConversionContext must no be null");
|
||||
Assert.notNull(accessor, "DocumentAccessor must no be null");
|
||||
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getPropertyValue(RelationalPersistentProperty property) {
|
||||
@@ -791,11 +903,39 @@ public class MappingRelationalConverter extends BasicRelationalConverter impleme
|
||||
return (T) contextToUse.convert(value, property.getTypeInformation());
|
||||
}
|
||||
|
||||
public RelationalPropertyValueProvider withContext(ConversionContext context) {
|
||||
|
||||
return context == this.context ? this
|
||||
: new RelationalPropertyValueProvider(context, accessor, evaluator, spELContext);
|
||||
@Override
|
||||
public boolean hasValue(RelationalPersistentProperty property) {
|
||||
return accessor.hasValue(property);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getValue(AggregatePath path) {
|
||||
|
||||
Object value = accessor.document().get(path.getColumnInfo().alias().getReference());
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return context.convert(value, path.getRequiredLeafProperty().getTypeInformation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasValue(AggregatePath path) {
|
||||
return accessor.document().get(path.getColumnInfo().alias().getReference()) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasValue(SqlIdentifier identifier) {
|
||||
return accessor().document().get(identifier.getReference()) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentValueProvider withContext(ConversionContext context) {
|
||||
return context == this.context ? this : new DocumentValueProvider(context, accessor, evaluator, spELContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,9 +36,9 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
class ObjectPath {
|
||||
public final class ObjectPath {
|
||||
|
||||
static final ObjectPath ROOT = new ObjectPath();
|
||||
public static final ObjectPath ROOT = new ObjectPath();
|
||||
|
||||
private final @Nullable ObjectPath parent;
|
||||
private final @Nullable Object object;
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.util.Assert;
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
class RowDocumentAccessor {
|
||||
public class RowDocumentAccessor {
|
||||
|
||||
private final RowDocument document;
|
||||
|
||||
@@ -103,7 +103,7 @@ class RowDocumentAccessor {
|
||||
|
||||
Assert.notNull(property, "Property must not be null");
|
||||
|
||||
return document.containsKey(getColumnName(property));
|
||||
return document.get(getColumnName(property)) != null;
|
||||
}
|
||||
|
||||
String getColumnName(RelationalPersistentProperty prop) {
|
||||
|
||||
@@ -42,10 +42,14 @@ public class RowDocument implements Map<String, Object> {
|
||||
this.delegate = new LinkedCaseInsensitiveMap<>();
|
||||
}
|
||||
|
||||
public RowDocument(int expectedSize) {
|
||||
this.delegate = new LinkedCaseInsensitiveMap<>(expectedSize);
|
||||
}
|
||||
|
||||
public RowDocument(Map<String, ? extends Object> map) {
|
||||
|
||||
this.delegate = new LinkedCaseInsensitiveMap<>();
|
||||
this.delegate.putAll(delegate);
|
||||
this.delegate.putAll(map);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user