Skip to content

Supported nested conversions for AggregateReference. #2062

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.0-1828-aggregate-ref-with-convertable-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data Relational Parent</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-jdbc-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.0-1828-aggregate-ref-with-convertable-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions spring-data-jdbc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-data-jdbc</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.0-1828-aggregate-ref-with-convertable-SNAPSHOT</version>

<name>Spring Data JDBC</name>
<description>Spring Data module for JDBC repositories.</description>
Expand All @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.0-1828-aggregate-ref-with-convertable-SNAPSHOT</version>
</parent>

<properties>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@
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.core.convert.converter.ConverterRegistry;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.jdbc.core.mapping.JdbcValue;
Expand Down Expand Up @@ -80,7 +79,7 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements
* {@link #MappingJdbcConverter(RelationalMappingContext, RelationResolver, CustomConversions, JdbcTypeFactory)}
* (MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and large objects into JDBC-specific types.
*
* @param context must not be {@literal null}.
* @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) {
Expand All @@ -91,19 +90,17 @@ public MappingJdbcConverter(RelationalMappingContext context, RelationResolver r

this.typeFactory = JdbcTypeFactory.unsupported();
this.relationResolver = relationResolver;

registerAggregateReferenceConverters();
}

/**
* Creates a new {@link MappingJdbcConverter} given {@link MappingContext}.
*
* @param context must not be {@literal null}.
* @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 typeFactory must not be {@literal null}
*/
public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver,
CustomConversions conversions, JdbcTypeFactory typeFactory) {
CustomConversions conversions, JdbcTypeFactory typeFactory) {

super(context, conversions);

Expand All @@ -112,14 +109,6 @@ public MappingJdbcConverter(RelationalMappingContext context, RelationResolver r

this.typeFactory = typeFactory;
this.relationResolver = relationResolver;

registerAggregateReferenceConverters();
}

private void registerAggregateReferenceConverters() {

ConverterRegistry registry = (ConverterRegistry) getConversionService();
AggregateReferenceConverters.getConvertersToRegister(getConversionService()).forEach(registry::addConverter);
}

@Nullable
Expand Down Expand Up @@ -184,34 +173,78 @@ private Class<?> doGetColumnType(RelationalPersistentProperty property) {
return componentColumnType;
}

/**
* Read and convert a single value that is coming from a database to the {@literal targetType} expected by the domain
* model.
*
* @param value a value as it is returned by the driver accessing the persistence store. May be {@code null}.
* @param targetType {@link TypeInformation} into which the value is to be converted. Must not be {@code null}.
* @return
*/
@Override
@Nullable
public Object readValue(@Nullable Object value, TypeInformation<?> type) {
public Object readValue(@Nullable Object value, TypeInformation<?> targetType) {

if (value == null) {
return value;
if (null == value) {
return null;
}

TypeInformation<?> originalTargetType = targetType;
value = readJdbcArray(value);
targetType = determineNestedTargetType(targetType);

return readToAggregateReference(getPotentiallyConvertedSimpleRead(value, targetType), originalTargetType);
}

/**
* Unwrap a Jdbc array, if such a value is provided
*/
private Object readJdbcArray(Object value) {

if (value instanceof Array array) {
try {
return super.readValue(array.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 array.getArray();
} catch (SQLException e) {
throw new FailedToAccessJdbcArrayException(e);
}
}

return super.readValue(value, type);
return value;
}

/**
* Determine the id type of an {@link AggregateReference} that the rest of the conversion infrastructure needs to use
* as a conversion target.
*/
private TypeInformation<?> determineNestedTargetType(TypeInformation<?> ultimateTargetType) {

if (AggregateReference.class.isAssignableFrom(ultimateTargetType.getType())) {
// the id type of a AggregateReference
return ultimateTargetType.getTypeArguments().get(1);
}
return ultimateTargetType;
}

/**
* Convert value to an {@link AggregateReference} if that is specified by the parameter targetType.
*/
private Object readToAggregateReference(Object value, TypeInformation<?> targetType) {

if (AggregateReference.class.isAssignableFrom(targetType.getType())) {
return AggregateReference.to(value);
}
return value;
}

@Override
@Nullable
public Object writeValue(@Nullable Object value, TypeInformation<?> type) {
@Override
protected Object getPotentiallyConvertedSimpleWrite(Object value, TypeInformation<?> type) {

if (value == null) {
return null;
if (value instanceof AggregateReference<?, ?> aggregateReference) {
return writeValue(aggregateReference.getId(), type);
}

return super.writeValue(value, type);
return super.getPotentiallyConvertedSimpleWrite(value, type);
}

private boolean canWriteAsJdbcValue(@Nullable Object value) {
Expand Down Expand Up @@ -285,7 +318,7 @@ public <R> R readAndResolve(TypeInformation<R> type, RowDocument source, Identif

@Override
protected RelationalPropertyValueProvider newValueProvider(RowDocumentAccessor documentAccessor,
ValueExpressionEvaluator evaluator, ConversionContext context) {
ValueExpressionEvaluator evaluator, ConversionContext context) {

if (context instanceof ResolvingConversionContext rcc) {

Expand All @@ -298,6 +331,12 @@ protected RelationalPropertyValueProvider newValueProvider(RowDocumentAccessor d
return super.newValueProvider(documentAccessor, evaluator, context);
}

private static class FailedToAccessJdbcArrayException extends NonTransientDataAccessException {
public FailedToAccessJdbcArrayException(SQLException e) {
super("Failed to read array", e);
}
}

/**
* {@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
Expand All @@ -314,7 +353,7 @@ class ResolvingRelationalPropertyValueProvider implements RelationalPropertyValu
private final Identifier identifier;

private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider delegate, RowDocumentAccessor accessor,
ResolvingConversionContext context, Identifier identifier) {
ResolvingConversionContext context, Identifier identifier) {

AggregatePath path = context.aggregatePath();

Expand All @@ -323,15 +362,15 @@ private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider dele
this.context = context;
this.identifier = path.isEntity()
? potentiallyAppendIdentifier(identifier, path.getRequiredLeafEntity(),
property -> delegate.getValue(path.append(property)))
property -> delegate.getValue(path.append(property)))
: identifier;
}

/**
* Conditionally append the identifier if the entity has an identifier property.
*/
static Identifier potentiallyAppendIdentifier(Identifier base, RelationalPersistentEntity<?> entity,
Function<RelationalPersistentProperty, Object> getter) {
Function<RelationalPersistentProperty, Object> getter) {

if (entity.hasIdProperty()) {

Expand Down Expand Up @@ -460,7 +499,7 @@ public RelationalPropertyValueProvider withContext(ConversionContext context) {

return context == this.context ? this
: new ResolvingRelationalPropertyValueProvider(delegate.withContext(context), accessor,
(ResolvingConversionContext) context, identifier);
(ResolvingConversionContext) context, identifier);
}
}

Expand All @@ -472,7 +511,7 @@ public RelationalPropertyValueProvider withContext(ConversionContext context) {
* @param identifier
*/
private record ResolvingConversionContext(ConversionContext delegate, AggregatePath aggregatePath,
Identifier identifier) implements ConversionContext {
Identifier identifier) implements ConversionContext {

@Override
public <S> S convert(Object source, TypeInformation<? extends S> typeHint) {
Expand Down
Loading