Skip to content

Commit 4e3120e

Browse files
mp911deschauder
authored andcommitted
Migrate JDBC to use RowDocument for reading aggregates.
Original pull request #1618 See #1554
1 parent 665ae6b commit 4e3120e

File tree

11 files changed

+677
-109
lines changed

11 files changed

+677
-109
lines changed

Diff for: spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java

+245-21
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import java.sql.ResultSet;
2121
import java.sql.SQLException;
2222
import java.sql.SQLType;
23+
import java.util.Iterator;
2324
import java.util.Map;
2425
import java.util.Optional;
26+
import java.util.function.Function;
2527

2628
import org.apache.commons.logging.Log;
2729
import org.apache.commons.logging.LogFactory;
@@ -44,13 +46,17 @@
4446
import org.springframework.data.mapping.model.SpELContext;
4547
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
4648
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
49+
import org.springframework.data.projection.EntityProjection;
4750
import org.springframework.data.relational.core.conversion.MappingRelationalConverter;
51+
import org.springframework.data.relational.core.conversion.ObjectPath;
4852
import org.springframework.data.relational.core.conversion.RelationalConverter;
53+
import org.springframework.data.relational.core.conversion.RowDocumentAccessor;
4954
import org.springframework.data.relational.core.mapping.AggregatePath;
5055
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
5156
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
5257
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
5358
import org.springframework.data.relational.core.sql.IdentifierProcessing;
59+
import org.springframework.data.relational.domain.RowDocument;
5460
import org.springframework.data.util.TypeInformation;
5561
import org.springframework.lang.Nullable;
5662
import org.springframework.util.Assert;
@@ -83,18 +89,15 @@ public class BasicJdbcConverter extends MappingRelationalConverter implements Jd
8389
private SpELContext spELContext;
8490

8591
/**
86-
* Creates a new {@link BasicJdbcConverter} given {@link MappingContext} and a
87-
* {@link JdbcTypeFactory#unsupported() no-op type factory} throwing {@link UnsupportedOperationException} on type
88-
* creation. Use
92+
* Creates a new {@link BasicJdbcConverter} given {@link MappingContext} and a {@link JdbcTypeFactory#unsupported()
93+
* no-op type factory} throwing {@link UnsupportedOperationException} on type creation. Use
8994
* {@link #BasicJdbcConverter(RelationalMappingContext, RelationResolver, CustomConversions, JdbcTypeFactory, IdentifierProcessing)}
9095
* (MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and large objects into JDBC-specific types.
9196
*
9297
* @param context must not be {@literal null}.
9398
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
9499
*/
95-
public BasicJdbcConverter(
96-
RelationalMappingContext context,
97-
RelationResolver relationResolver) {
100+
public BasicJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver) {
98101

99102
super(context, new JdbcCustomConversions());
100103

@@ -115,10 +118,8 @@ public BasicJdbcConverter(
115118
* @param identifierProcessing must not be {@literal null}
116119
* @since 2.0
117120
*/
118-
public BasicJdbcConverter(
119-
RelationalMappingContext context,
120-
RelationResolver relationResolver, CustomConversions conversions, JdbcTypeFactory typeFactory,
121-
IdentifierProcessing identifierProcessing) {
121+
public BasicJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver,
122+
CustomConversions conversions, JdbcTypeFactory typeFactory, IdentifierProcessing identifierProcessing) {
122123

123124
super(context, conversions);
124125

@@ -300,16 +301,241 @@ private JdbcValue tryToConvertToJdbcValue(@Nullable Object value) {
300301

301302
@Override
302303
public <T> T mapRow(RelationalPersistentEntity<T> entity, ResultSet resultSet, Object key) {
303-
return new ReadingContext<T>(getMappingContext().getAggregatePath( entity),
304-
new ResultSetAccessor(resultSet), Identifier.empty(), key).mapRow();
304+
return new ReadingContext<T>(getMappingContext().getAggregatePath(entity), new ResultSetAccessor(resultSet),
305+
Identifier.empty(), key).mapRow();
305306
}
306307

307-
308308
@Override
309309
public <T> T mapRow(AggregatePath path, ResultSet resultSet, Identifier identifier, Object key) {
310310
return new ReadingContext<T>(path, new ResultSetAccessor(resultSet), identifier, key).mapRow();
311311
}
312312

313+
@Override
314+
public <R> R projectAndResolve(EntityProjection<R, ?> projection, RowDocument document) {
315+
316+
RelationalPersistentEntity<?> entity = getMappingContext()
317+
.getRequiredPersistentEntity(projection.getActualDomainType());
318+
ResolvingConversionContext context = new ResolvingConversionContext(newProjectingConversionContext(projection),
319+
getMappingContext().getAggregatePath(entity), Identifier.empty());
320+
321+
return doReadProjection(context, document, projection);
322+
}
323+
324+
@SuppressWarnings("unchecked")
325+
@Override
326+
public <R> R readAndResolve(Class<R> type, RowDocument source, Identifier identifier) {
327+
328+
RelationalPersistentEntity<R> entity = (RelationalPersistentEntity<R>) getMappingContext()
329+
.getRequiredPersistentEntity(type);
330+
AggregatePath path = getMappingContext().getAggregatePath(entity);
331+
Identifier identifierToUse = ResolvingRelationalPropertyValueProvider.potentiallyAppendIdentifier(identifier,
332+
entity, it -> source.get(it.getColumnName().getReference()));
333+
ResolvingConversionContext context = new ResolvingConversionContext(getConversionContext(ObjectPath.ROOT), path,
334+
identifierToUse);
335+
336+
return readAggregate(context, source, entity.getTypeInformation());
337+
}
338+
339+
@Override
340+
protected RelationalPropertyValueProvider newValueProvider(RowDocumentAccessor documentAccessor,
341+
SpELExpressionEvaluator evaluator, ConversionContext context) {
342+
343+
if (context instanceof ResolvingConversionContext rcc) {
344+
345+
AggregatePathValueProvider delegate = (AggregatePathValueProvider) super.newValueProvider(documentAccessor,
346+
evaluator, context);
347+
348+
return new ResolvingRelationalPropertyValueProvider(delegate, documentAccessor, rcc, rcc.identifier());
349+
}
350+
351+
return super.newValueProvider(documentAccessor, evaluator, context);
352+
}
353+
354+
/**
355+
* {@link RelationalPropertyValueProvider} using a resolving context to lookup relations. This is highly
356+
* context-sensitive. Note that the identifier is held here because of a chicken and egg problem, while
357+
* {@link ResolvingConversionContext} hols the {@link AggregatePath}.
358+
*/
359+
class ResolvingRelationalPropertyValueProvider implements RelationalPropertyValueProvider {
360+
361+
private final AggregatePathValueProvider delegate;
362+
363+
private final RowDocumentAccessor accessor;
364+
365+
private final ResolvingConversionContext context;
366+
367+
private final Identifier identifier;
368+
369+
private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider delegate, RowDocumentAccessor accessor,
370+
ResolvingConversionContext context, Identifier identifier) {
371+
372+
AggregatePath path = context.aggregatePath();
373+
374+
this.delegate = delegate;
375+
this.accessor = accessor;
376+
this.context = context;
377+
this.identifier = path.isEntity()
378+
? potentiallyAppendIdentifier(identifier, path.getRequiredLeafEntity(), delegate::getPropertyValue)
379+
: identifier;
380+
}
381+
382+
/**
383+
* Conditionally append the identifier if the entity has an identifier property.
384+
*/
385+
static Identifier potentiallyAppendIdentifier(Identifier base, RelationalPersistentEntity<?> entity,
386+
Function<RelationalPersistentProperty, Object> getter) {
387+
388+
if (entity.hasIdProperty()) {
389+
390+
RelationalPersistentProperty idProperty = entity.getRequiredIdProperty();
391+
Object propertyValue = getter.apply(idProperty);
392+
393+
if (propertyValue != null) {
394+
return base.withPart(idProperty.getColumnName(), propertyValue, idProperty.getType());
395+
}
396+
}
397+
398+
return base;
399+
}
400+
401+
@SuppressWarnings("unchecked")
402+
@Nullable
403+
@Override
404+
public <T> T getPropertyValue(RelationalPersistentProperty property) {
405+
406+
AggregatePath aggregatePath = this.context.aggregatePath();
407+
408+
if (getConversions().isSimpleType(property.getActualType())) {
409+
return (T) delegate.getValue(aggregatePath);
410+
}
411+
412+
if (property.isEntity()) {
413+
414+
if (property.isCollectionLike() || property.isMap()) {
415+
416+
Identifier identifier1 = this.identifier;
417+
if (property.getOwner().hasIdProperty()) {
418+
Object id = this.identifier.get(property.getOwner().getRequiredIdProperty().getColumnName());
419+
420+
if (id != null) {
421+
identifier1 = Identifier.of(aggregatePath.getTableInfo().reverseColumnInfo().name(), id, Object.class);
422+
}
423+
}
424+
425+
Iterable<Object> allByPath = relationResolver.findAllByPath(identifier1,
426+
aggregatePath.getRequiredPersistentPropertyPath());
427+
428+
if (property.isCollectionLike()) {
429+
return (T) allByPath;
430+
}
431+
432+
if (property.isMap()) {
433+
return (T) ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(allByPath);
434+
}
435+
436+
Iterator<Object> iterator = allByPath.iterator();
437+
if (iterator.hasNext()) {
438+
return (T) iterator.next();
439+
}
440+
441+
return null;
442+
}
443+
444+
return hasValue(property) ? (T) readAggregate(this.context, accessor, property.getTypeInformation()) : null;
445+
}
446+
447+
return (T) delegate.getValue(aggregatePath);
448+
}
449+
450+
@Override
451+
public boolean hasValue(RelationalPersistentProperty property) {
452+
453+
if (property.isCollectionLike() || property.isMap()) {
454+
// attempt relation fetch
455+
return true;
456+
}
457+
458+
AggregatePath aggregatePath = context.aggregatePath();
459+
460+
if (property.isEntity()) {
461+
462+
RelationalPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(property);
463+
if (entity.hasIdProperty()) {
464+
465+
RelationalPersistentProperty referenceId = entity.getRequiredIdProperty();
466+
AggregatePath toUse = aggregatePath.append(referenceId);
467+
return delegate.hasValue(toUse);
468+
}
469+
470+
return delegate.hasValue(aggregatePath.getTableInfo().reverseColumnInfo().alias());
471+
}
472+
473+
return delegate.hasValue(aggregatePath);
474+
}
475+
476+
@Override
477+
public RelationalPropertyValueProvider withContext(ConversionContext context) {
478+
479+
return context == this.context ? this
480+
: new ResolvingRelationalPropertyValueProvider(delegate.withContext(context), accessor,
481+
(ResolvingConversionContext) context, identifier);
482+
}
483+
484+
}
485+
486+
/**
487+
* Marker object to indicate that the property value provider should resolve relations.
488+
*
489+
* @param delegate
490+
* @param aggregatePath
491+
* @param identifier
492+
*/
493+
private record ResolvingConversionContext(ConversionContext delegate, AggregatePath aggregatePath,
494+
Identifier identifier) implements ConversionContext {
495+
496+
@Override
497+
public <S> S convert(Object source, TypeInformation<? extends S> typeHint) {
498+
return delegate.convert(source, typeHint);
499+
}
500+
501+
@Override
502+
public <S> S convert(Object source, TypeInformation<? extends S> typeHint, ConversionContext context) {
503+
return delegate.convert(source, typeHint, context);
504+
}
505+
506+
@Override
507+
public ResolvingConversionContext forProperty(String name) {
508+
RelationalPersistentProperty property = aggregatePath.getRequiredLeafEntity().getRequiredPersistentProperty(name);
509+
return forProperty(property);
510+
}
511+
512+
@Override
513+
public ResolvingConversionContext forProperty(RelationalPersistentProperty property) {
514+
ConversionContext nested = delegate.forProperty(property);
515+
return new ResolvingConversionContext(nested, aggregatePath.append(property), identifier);
516+
}
517+
518+
@Override
519+
public ResolvingConversionContext withPath(ObjectPath currentPath) {
520+
return new ResolvingConversionContext(delegate.withPath(currentPath), aggregatePath, identifier);
521+
}
522+
523+
@Override
524+
public ObjectPath getPath() {
525+
return delegate.getPath();
526+
}
527+
528+
@Override
529+
public CustomConversions getCustomConversions() {
530+
return delegate.getCustomConversions();
531+
}
532+
533+
@Override
534+
public RelationalConverter getSourceConverter() {
535+
return delegate.getSourceConverter();
536+
}
537+
}
538+
313539
static Object[] requireObjectArray(Object source) {
314540

315541
Assert.isTrue(source.getClass().isArray(), "Source object is not an array");
@@ -361,25 +587,23 @@ private class ReadingContext<T> {
361587
private final ResultSetAccessor accessor;
362588

363589
@SuppressWarnings("unchecked")
364-
private ReadingContext(AggregatePath rootPath, ResultSetAccessor accessor, Identifier identifier,
365-
Object key) {
590+
private ReadingContext(AggregatePath rootPath, ResultSetAccessor accessor, Identifier identifier, Object key) {
366591
RelationalPersistentEntity<T> entity = (RelationalPersistentEntity<T>) rootPath.getLeafEntity();
367592

368593
Assert.notNull(entity, "The rootPath must point to an entity");
369594

370595
this.entity = entity;
371596
this.rootPath = rootPath;
372-
this.path = getMappingContext().getAggregatePath( this.entity);
597+
this.path = getMappingContext().getAggregatePath(this.entity);
373598
this.identifier = identifier;
374599
this.key = key;
375600
this.propertyValueProvider = new JdbcPropertyValueProvider(path, accessor);
376601
this.backReferencePropertyValueProvider = new JdbcBackReferencePropertyValueProvider(path, accessor);
377602
this.accessor = accessor;
378603
}
379604

380-
private ReadingContext(RelationalPersistentEntity<T> entity, AggregatePath rootPath,
381-
AggregatePath path, Identifier identifier, Object key,
382-
JdbcPropertyValueProvider propertyValueProvider,
605+
private ReadingContext(RelationalPersistentEntity<T> entity, AggregatePath rootPath, AggregatePath path,
606+
Identifier identifier, Object key, JdbcPropertyValueProvider propertyValueProvider,
383607
JdbcBackReferencePropertyValueProvider backReferencePropertyValueProvider, ResultSetAccessor accessor) {
384608

385609
this.entity = entity;
@@ -396,8 +620,8 @@ private <S> ReadingContext<S> extendBy(RelationalPersistentProperty property) {
396620

397621
return new ReadingContext<>(
398622
(RelationalPersistentEntity<S>) getMappingContext().getRequiredPersistentEntity(property.getActualType()),
399-
rootPath.append(property), path.append(property), identifier, key,
400-
propertyValueProvider.extendBy(property), backReferencePropertyValueProvider.extendBy(property), accessor);
623+
rootPath.append(property), path.append(property), identifier, key, propertyValueProvider.extendBy(property),
624+
backReferencePropertyValueProvider.extendBy(property), accessor);
401625
}
402626

403627
T mapRow() {

0 commit comments

Comments
 (0)