Skip to content

Commit 41b37a6

Browse files
mp911deschauder
authored andcommitted
Add support for multi-level projections using EntityProjection.
We now support multi-level projections by introspecting the result and the domain type and read projections directly into a DTO or a backing map for interface projections. Original pull request #1618 Closes #1554
1 parent 9c36eb9 commit 41b37a6

File tree

10 files changed

+540
-9
lines changed

10 files changed

+540
-9
lines changed

spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java

+17
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import org.springframework.data.mapping.model.EntityInstantiators;
4040
import org.springframework.data.mapping.model.ParameterValueProvider;
4141
import org.springframework.data.mapping.model.SimpleTypeHolder;
42+
import org.springframework.data.projection.EntityProjection;
43+
import org.springframework.data.projection.ProjectionFactory;
4244
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
4345
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
4446
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@@ -126,6 +128,21 @@ public <T> PersistentPropertyPathAccessor<T> getPropertyAccessor(PersistentEntit
126128
return new ConvertingPropertyAccessor<>(accessor, conversionService);
127129
}
128130

131+
@Override
132+
public <M, D> EntityProjection<M, D> introspectProjection(Class<M> resultType, Class<D> entityType) {
133+
throw new UnsupportedOperationException();
134+
}
135+
136+
@Override
137+
public ProjectionFactory getProjectionFactory() {
138+
throw new UnsupportedOperationException();
139+
}
140+
141+
@Override
142+
public <R> R project(EntityProjection<R, ?> descriptor, RowDocument document) {
143+
throw new UnsupportedOperationException();
144+
}
145+
129146
@Override
130147
public <R> R read(Class<R> type, RowDocument source) {
131148
throw new UnsupportedOperationException();

spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java

+231-4
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,22 @@
1717

1818
import java.util.ArrayList;
1919
import java.util.Collection;
20+
import java.util.LinkedHashMap;
2021
import java.util.List;
2122
import java.util.Map;
2223
import java.util.function.Predicate;
2324

25+
import org.springframework.beans.BeansException;
26+
import org.springframework.context.ApplicationContext;
27+
import org.springframework.context.ApplicationContextAware;
2428
import org.springframework.core.CollectionFactory;
2529
import org.springframework.core.convert.ConversionService;
2630
import org.springframework.data.convert.CustomConversions;
2731
import org.springframework.data.mapping.InstanceCreatorMetadata;
2832
import org.springframework.data.mapping.MappingException;
2933
import org.springframework.data.mapping.Parameter;
3034
import org.springframework.data.mapping.PersistentEntity;
35+
import org.springframework.data.mapping.PersistentProperty;
3136
import org.springframework.data.mapping.PersistentPropertyAccessor;
3237
import org.springframework.data.mapping.context.MappingContext;
3338
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
@@ -39,12 +44,19 @@
3944
import org.springframework.data.mapping.model.SpELContext;
4045
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
4146
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
47+
import org.springframework.data.projection.EntityProjection;
48+
import org.springframework.data.projection.EntityProjectionIntrospector;
49+
import org.springframework.data.projection.EntityProjectionIntrospector.ProjectionPredicate;
50+
import org.springframework.data.projection.ProjectionFactory;
51+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
4252
import org.springframework.data.relational.core.mapping.Embedded;
4353
import org.springframework.data.relational.core.mapping.Embedded.OnEmpty;
54+
import org.springframework.data.relational.core.mapping.PersistentPropertyTranslator;
4455
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
4556
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
4657
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
4758
import org.springframework.data.relational.domain.RowDocument;
59+
import org.springframework.data.util.Predicates;
4860
import org.springframework.data.util.TypeInformation;
4961
import org.springframework.lang.Nullable;
5062
import org.springframework.util.Assert;
@@ -57,10 +69,14 @@
5769
* @author Mark Paluch
5870
* @since 3.2
5971
*/
60-
public class MappingRelationalConverter extends BasicRelationalConverter {
72+
public class MappingRelationalConverter extends BasicRelationalConverter implements ApplicationContextAware {
6173

6274
private SpELContext spELContext;
6375

76+
private final SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
77+
78+
private final EntityProjectionIntrospector introspector;
79+
6480
/**
6581
* Creates a new {@link MappingRelationalConverter} given the new {@link RelationalMappingContext}.
6682
*
@@ -71,6 +87,7 @@ public MappingRelationalConverter(RelationalMappingContext context) {
7187
super(context);
7288

7389
this.spELContext = new SpELContext(DocumentPropertyAccessor.INSTANCE);
90+
this.introspector = createIntrospector(projectionFactory, getConversions(), getMappingContext());
7491
}
7592

7693
/**
@@ -85,6 +102,29 @@ public MappingRelationalConverter(RelationalMappingContext context, CustomConver
85102
super(context, conversions);
86103

87104
this.spELContext = new SpELContext(DocumentPropertyAccessor.INSTANCE);
105+
this.introspector = createIntrospector(projectionFactory, getConversions(), getMappingContext());
106+
107+
}
108+
109+
private static EntityProjectionIntrospector createIntrospector(ProjectionFactory projectionFactory,
110+
CustomConversions conversions, MappingContext<?, ?> mappingContext) {
111+
112+
return EntityProjectionIntrospector.create(projectionFactory,
113+
ProjectionPredicate.typeHierarchy().and((target, underlyingType) -> !conversions.isSimpleType(target)),
114+
mappingContext);
115+
}
116+
117+
@Override
118+
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
119+
120+
this.spELContext = new SpELContext(this.spELContext, applicationContext);
121+
this.projectionFactory.setBeanFactory(applicationContext);
122+
this.projectionFactory.setBeanClassLoader(applicationContext.getClassLoader());
123+
}
124+
125+
@Override
126+
public ProjectionFactory getProjectionFactory() {
127+
return this.projectionFactory;
88128
}
89129

90130
/**
@@ -100,6 +140,128 @@ protected ConversionContext getConversionContext(ObjectPath path) {
100140
this::readMap, this::getPotentiallyConvertedSimpleRead);
101141
}
102142

143+
@Override
144+
public <M, D> EntityProjection<M, D> introspectProjection(Class<M> resultType, Class<D> entityType) {
145+
146+
RelationalPersistentEntity<?> persistentEntity = getMappingContext().getPersistentEntity(entityType);
147+
if (persistentEntity == null && !resultType.isInterface()
148+
|| ClassUtils.isAssignable(RowDocument.class, resultType)) {
149+
return (EntityProjection) EntityProjection.nonProjecting(resultType);
150+
}
151+
return introspector.introspect(resultType, entityType);
152+
}
153+
154+
@Override
155+
public <R> R project(EntityProjection<R, ?> projection, RowDocument document) {
156+
157+
if (!projection.isProjection()) { // backed by real object
158+
159+
TypeInformation<?> typeToRead = projection.getMappedType().getType().isInterface() ? projection.getDomainType()
160+
: projection.getMappedType();
161+
return (R) read(typeToRead, document);
162+
}
163+
164+
ProjectingConversionContext context = new ProjectingConversionContext(this, getConversions(), ObjectPath.ROOT,
165+
this::readCollectionOrArray, this::readMap, this::getPotentiallyConvertedSimpleRead, projection);
166+
167+
return doReadProjection(context, document, projection);
168+
}
169+
170+
@SuppressWarnings("unchecked")
171+
private <R> R doReadProjection(ConversionContext context, RowDocument document, EntityProjection<R, ?> projection) {
172+
173+
RelationalPersistentEntity<?> entity = getMappingContext()
174+
.getRequiredPersistentEntity(projection.getActualDomainType());
175+
TypeInformation<?> mappedType = projection.getActualMappedType();
176+
RelationalPersistentEntity<R> mappedEntity = (RelationalPersistentEntity<R>) getMappingContext()
177+
.getPersistentEntity(mappedType);
178+
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(document, spELContext);
179+
180+
boolean isInterfaceProjection = mappedType.getType().isInterface();
181+
if (isInterfaceProjection) {
182+
183+
PersistentPropertyTranslator propertyTranslator = PersistentPropertyTranslator.create(mappedEntity);
184+
RowDocumentAccessor documentAccessor = new RowDocumentAccessor(document);
185+
PersistentPropertyAccessor<?> accessor = new MapPersistentPropertyAccessor();
186+
187+
PersistentPropertyAccessor<?> convertingAccessor = PropertyTranslatingPropertyAccessor
188+
.create(new ConvertingPropertyAccessor<>(accessor, getConversionService()), propertyTranslator);
189+
RelationalPropertyValueProvider valueProvider = new RelationalPropertyValueProvider(context, documentAccessor,
190+
evaluator, spELContext);
191+
192+
readProperties(context, entity, convertingAccessor, documentAccessor, valueProvider, Predicates.isTrue());
193+
return (R) projectionFactory.createProjection(mappedType.getType(), accessor.getBean());
194+
}
195+
196+
// DTO projection
197+
if (mappedEntity == null) {
198+
throw new MappingException(String.format("No mapping metadata found for %s", mappedType.getType().getName()));
199+
}
200+
201+
// create target instance, merge metadata from underlying DTO type
202+
PersistentPropertyTranslator propertyTranslator = PersistentPropertyTranslator.create(entity,
203+
Predicates.negate(RelationalPersistentProperty::hasExplicitColumnName));
204+
RowDocumentAccessor documentAccessor = new RowDocumentAccessor(document) {
205+
206+
@Override
207+
String getColumnName(RelationalPersistentProperty prop) {
208+
return propertyTranslator.translate(prop).getColumnName().getReference();
209+
}
210+
};
211+
212+
InstanceCreatorMetadata<RelationalPersistentProperty> instanceCreatorMetadata = mappedEntity
213+
.getInstanceCreatorMetadata();
214+
ParameterValueProvider<RelationalPersistentProperty> provider = instanceCreatorMetadata != null
215+
&& instanceCreatorMetadata.hasParameters()
216+
? getParameterProvider(context, mappedEntity, documentAccessor, evaluator)
217+
: NoOpParameterValueProvider.INSTANCE;
218+
219+
EntityInstantiator instantiator = getEntityInstantiators().getInstantiatorFor(mappedEntity);
220+
R instance = instantiator.createInstance(mappedEntity, provider);
221+
PersistentPropertyAccessor<R> accessor = mappedEntity.getPropertyAccessor(instance);
222+
223+
populateProperties(context, mappedEntity, documentAccessor, evaluator, instance);
224+
225+
PersistentPropertyAccessor<?> convertingAccessor = new ConvertingPropertyAccessor<>(accessor,
226+
getConversionService());
227+
RelationalPropertyValueProvider valueProvider = new RelationalPropertyValueProvider(context, documentAccessor,
228+
evaluator, spELContext);
229+
230+
readProperties(context, mappedEntity, convertingAccessor, documentAccessor, valueProvider, Predicates.isTrue());
231+
232+
return accessor.getBean();
233+
}
234+
235+
private Object doReadOrProject(ConversionContext context, RowDocument source, TypeInformation<?> typeHint,
236+
EntityProjection<?, ?> typeDescriptor) {
237+
238+
if (typeDescriptor.isProjection()) {
239+
return doReadProjection(context, source, typeDescriptor);
240+
}
241+
242+
return readAggregate(context, source, typeHint);
243+
}
244+
245+
static class MapPersistentPropertyAccessor implements PersistentPropertyAccessor<Map<String, Object>> {
246+
247+
Map<String, Object> map = new LinkedHashMap<>();
248+
249+
@Override
250+
public void setProperty(PersistentProperty<?> persistentProperty, Object o) {
251+
map.put(persistentProperty.getName(), o);
252+
}
253+
254+
@Override
255+
public Object getProperty(PersistentProperty<?> persistentProperty) {
256+
return map.get(persistentProperty.getName());
257+
}
258+
259+
@Override
260+
public Map<String, Object> getBean() {
261+
return map;
262+
}
263+
}
264+
103265
/**
104266
* Read a {@link RowDocument} into the requested {@link Class aggregate type}.
105267
*
@@ -295,15 +457,14 @@ private <S> S populateProperties(ConversionContext context, RelationalPersistent
295457
evaluator, spELContext);
296458

297459
Predicate<RelationalPersistentProperty> propertyFilter = isConstructorArgument(entity).negate();
298-
readProperties(contextToUse, entity, accessor, documentAccessor, valueProvider, evaluator, propertyFilter);
460+
readProperties(contextToUse, entity, accessor, documentAccessor, valueProvider, propertyFilter);
299461

300462
return accessor.getBean();
301463
}
302464

303465
private void readProperties(ConversionContext context, RelationalPersistentEntity<?> entity,
304466
PersistentPropertyAccessor<?> accessor, RowDocumentAccessor documentAccessor,
305-
RelationalPropertyValueProvider valueProvider, SpELExpressionEvaluator evaluator,
306-
Predicate<RelationalPersistentProperty> propertyFilter) {
467+
RelationalPropertyValueProvider valueProvider, Predicate<RelationalPersistentProperty> propertyFilter) {
307468

308469
for (RelationalPersistentProperty prop : entity) {
309470

@@ -475,6 +636,44 @@ interface ContainerValueConverter<T> {
475636

476637
}
477638

639+
/**
640+
* @since 3.4.3
641+
*/
642+
class ProjectingConversionContext extends DefaultConversionContext {
643+
644+
private final EntityProjection<?, ?> returnedTypeDescriptor;
645+
646+
ProjectingConversionContext(RelationalConverter sourceConverter, CustomConversions customConversions,
647+
ObjectPath path, ContainerValueConverter<Collection<?>> collectionConverter,
648+
ContainerValueConverter<Map<?, ?>> mapConverter, ValueConverter<Object> elementConverter,
649+
EntityProjection<?, ?> projection) {
650+
super(sourceConverter, customConversions, path,
651+
(context, source, typeHint) -> doReadOrProject(context, source, typeHint, projection),
652+
653+
collectionConverter, mapConverter, elementConverter);
654+
this.returnedTypeDescriptor = projection;
655+
}
656+
657+
@Override
658+
public ConversionContext forProperty(String name) {
659+
660+
EntityProjection<?, ?> property = returnedTypeDescriptor.findProperty(name);
661+
if (property == null) {
662+
return new DefaultConversionContext(sourceConverter, conversions, objectPath,
663+
MappingRelationalConverter.this::readAggregate, collectionConverter, mapConverter, elementConverter);
664+
}
665+
666+
return new ProjectingConversionContext(sourceConverter, conversions, objectPath, collectionConverter,
667+
mapConverter, elementConverter, property);
668+
}
669+
670+
@Override
671+
public ConversionContext withPath(ObjectPath currentPath) {
672+
return new ProjectingConversionContext(sourceConverter, conversions, currentPath, collectionConverter,
673+
mapConverter, elementConverter, returnedTypeDescriptor);
674+
}
675+
}
676+
478677
/**
479678
* Conversion context defining an interface for graph-traversal-based conversion of row documents. Entrypoint for
480679
* recursive conversion of {@link RowDocument} and other types.
@@ -633,4 +832,32 @@ protected <T> T potentiallyConvertSpelValue(Object object, Parameter<T, Relation
633832
}
634833
}
635834

835+
private record PropertyTranslatingPropertyAccessor<T> (PersistentPropertyAccessor<T> delegate,
836+
PersistentPropertyTranslator propertyTranslator) implements PersistentPropertyAccessor<T> {
837+
838+
static <T> PersistentPropertyAccessor<T> create(PersistentPropertyAccessor<T> delegate,
839+
PersistentPropertyTranslator propertyTranslator) {
840+
return new PropertyTranslatingPropertyAccessor<>(delegate, propertyTranslator);
841+
}
842+
843+
@Override
844+
public void setProperty(PersistentProperty<?> property, @Nullable Object value) {
845+
delegate.setProperty(translate(property), value);
846+
}
847+
848+
@Override
849+
public Object getProperty(PersistentProperty<?> property) {
850+
return delegate.getProperty(translate(property));
851+
}
852+
853+
@Override
854+
public T getBean() {
855+
return delegate.getBean();
856+
}
857+
858+
private RelationalPersistentProperty translate(PersistentProperty<?> property) {
859+
return propertyTranslator.translate((RelationalPersistentProperty) property);
860+
}
861+
}
862+
636863
}

0 commit comments

Comments
 (0)