Skip to content

Commit c131358

Browse files
GH-2270 - Support findAs DTO based projection.
This closes #2270.
1 parent 925021a commit c131358

File tree

11 files changed

+223
-70
lines changed

11 files changed

+223
-70
lines changed

src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java

+52-24
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.Map;
3131
import java.util.Optional;
3232
import java.util.Set;
33+
import java.util.function.BiFunction;
3334
import java.util.function.Consumer;
3435
import java.util.function.Function;
3536
import java.util.function.Predicate;
@@ -47,6 +48,8 @@
4748
import org.neo4j.driver.summary.ResultSummary;
4849
import org.neo4j.driver.summary.SummaryCounters;
4950
import org.neo4j.driver.types.Entity;
51+
import org.neo4j.driver.types.MapAccessor;
52+
import org.neo4j.driver.types.TypeSystem;
5053
import org.springframework.beans.BeansException;
5154
import org.springframework.beans.factory.BeanClassLoaderAware;
5255
import org.springframework.beans.factory.BeanFactory;
@@ -61,6 +64,8 @@
6164
import org.springframework.data.neo4j.core.mapping.Constants;
6265
import org.springframework.data.neo4j.core.mapping.CreateRelationshipStatementHolder;
6366
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
67+
import org.springframework.data.neo4j.core.mapping.DtoInstantiatingConverter;
68+
import org.springframework.data.neo4j.core.mapping.EntityInstanceWithSource;
6469
import org.springframework.data.neo4j.core.mapping.MappingSupport;
6570
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
6671
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
@@ -190,24 +195,29 @@ public long count(String cypherQuery, Map<String, Object> parameters) {
190195

191196
@Override
192197
public <T> List<T> findAll(Class<T> domainType) {
198+
199+
return doFindAll(domainType, null);
200+
}
201+
202+
private <T> List<T> doFindAll(Class<T> domainType, Class<?> resultType) {
193203
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getPersistentEntity(domainType);
194-
return createExecutableQuery(domainType, QueryFragmentsAndParameters.forFindAll(entityMetaData))
204+
return createExecutableQuery(domainType, resultType, QueryFragmentsAndParameters.forFindAll(entityMetaData))
195205
.getResults();
196206
}
197207

198208
@Override
199209
public <T> List<T> findAll(Statement statement, Class<T> domainType) {
200-
return createExecutableQuery(domainType, statement, Collections.emptyMap()).getResults();
210+
return createExecutableQuery(domainType, statement).getResults();
201211
}
202212

203213
@Override
204214
public <T> List<T> findAll(Statement statement, Map<String, Object> parameters, Class<T> domainType) {
205-
return createExecutableQuery(domainType, statement, parameters).getResults();
215+
return createExecutableQuery(domainType, null, statement, parameters).getResults();
206216
}
207217

208218
@Override
209219
public <T> Optional<T> findOne(Statement statement, Map<String, Object> parameters, Class<T> domainType) {
210-
return createExecutableQuery(domainType, statement, parameters).getSingleResult();
220+
return createExecutableQuery(domainType, null, statement, parameters).getSingleResult();
211221
}
212222

213223
@Override
@@ -217,26 +227,27 @@ public <T> List<T> findAll(String cypherQuery, Class<T> domainType) {
217227

218228
@Override
219229
public <T> List<T> findAll(String cypherQuery, Map<String, Object> parameters, Class<T> domainType) {
220-
return createExecutableQuery(domainType, cypherQuery, parameters).getResults();
230+
return createExecutableQuery(domainType, null, cypherQuery, parameters).getResults();
221231
}
222232

223233
@Override
224234
public <T> Optional<T> findOne(String cypherQuery, Map<String, Object> parameters, Class<T> domainType) {
225-
return createExecutableQuery(domainType, cypherQuery, parameters).getSingleResult();
235+
return createExecutableQuery(domainType, null, cypherQuery, parameters).getSingleResult();
226236
}
227237

228238
@Override
229239
public <T> ExecutableFind<T> find(Class<T> domainType) {
230240
return new FluentFindOperationSupport(this).find(domainType);
231241
}
232242

233-
<T, R> List<R> doFind(String cypherQuery, Map<String, Object> parameters, Class<T> domainType, Class<R> resultType, TemplateSupport.FetchType fetchType) {
243+
@SuppressWarnings("unchecked")
244+
<T, R> List<R> doFind(@Nullable String cypherQuery, @Nullable Map<String, Object> parameters, Class<T> domainType, Class<R> resultType, TemplateSupport.FetchType fetchType) {
234245

235246
List<T> intermediaResults = Collections.emptyList();
236247
if (cypherQuery == null && fetchType == TemplateSupport.FetchType.ALL) {
237-
intermediaResults = findAll(domainType);
248+
intermediaResults = doFindAll(domainType, resultType);
238249
} else {
239-
ExecutableQuery<T> executableQuery = createExecutableQuery(domainType, cypherQuery,
250+
ExecutableQuery<T> executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
240251
parameters == null ? Collections.emptyMap() : parameters);
241252
switch (fetchType) {
242253
case ALL:
@@ -253,16 +264,25 @@ <T, R> List<R> doFind(String cypherQuery, Map<String, Object> parameters, Class<
253264
return (List<R>) intermediaResults;
254265
}
255266

267+
if (resultType.isInterface()) {
268+
return intermediaResults.stream()
269+
.map(instance -> projectionFactory.createProjection(resultType, instance))
270+
.collect(Collectors.toList());
271+
}
272+
273+
DtoInstantiatingConverter converter = new DtoInstantiatingConverter(resultType, neo4jMappingContext);
256274
return intermediaResults.stream()
257-
.map(instance -> projectionFactory.createProjection(resultType, instance))
275+
.map(EntityInstanceWithSource.class::cast)
276+
.map(converter::convert)
277+
.map(v -> (R) v)
258278
.collect(Collectors.toList());
259279
}
260280

261281
@Override
262282
public <T> Optional<T> findById(Object id, Class<T> domainType) {
263283
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getPersistentEntity(domainType);
264284

265-
return createExecutableQuery(domainType,
285+
return createExecutableQuery(domainType, null,
266286
QueryFragmentsAndParameters.forFindById(entityMetaData,
267287
convertIdValues(entityMetaData.getRequiredIdProperty(), id)))
268288
.getSingleResult();
@@ -272,7 +292,7 @@ public <T> Optional<T> findById(Object id, Class<T> domainType) {
272292
public <T> List<T> findAllById(Iterable<?> ids, Class<T> domainType) {
273293
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getPersistentEntity(domainType);
274294

275-
return createExecutableQuery(domainType,
295+
return createExecutableQuery(domainType, null,
276296
QueryFragmentsAndParameters.forFindByAllId(
277297
entityMetaData, convertIdValues(entityMetaData.getRequiredIdProperty(), ids)))
278298
.getResults();
@@ -520,7 +540,7 @@ public <T> void deleteByIdWithVersion(Object id, Class<T> domainType, Neo4jPersi
520540
parameters.put(nameOfParameter, convertIdValues(entityMetaData.getRequiredIdProperty(), id));
521541
parameters.put(Constants.NAME_OF_VERSION_PARAM, versionValue);
522542

523-
createExecutableQuery(domainType, statement, parameters).getSingleResult().orElseThrow(
543+
createExecutableQuery(domainType, null, statement, parameters).getSingleResult().orElseThrow(
524544
() -> new OptimisticLockingFailureException(OPTIMISTIC_LOCKING_ERROR_MESSAGE)
525545
);
526546

@@ -558,23 +578,28 @@ public void deleteAll(Class<?> domainType) {
558578
summary.counters().relationshipsDeleted()));
559579
}
560580

561-
private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, String cypherStatement) {
562-
return createExecutableQuery(domainType, cypherStatement, Collections.emptyMap());
581+
private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, Statement statement) {
582+
return createExecutableQuery(domainType, null, statement, Collections.emptyMap());
583+
}
584+
585+
private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, String cyperQuery) {
586+
return createExecutableQuery(domainType, null, cyperQuery, Collections.emptyMap());
563587
}
564588

565-
private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, Statement statement, Map<String, Object> parameters) {
589+
private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, @Nullable Class<?> resultType, Statement statement, Map<String, Object> parameters) {
566590

567-
return createExecutableQuery(domainType, renderer.render(statement), TemplateSupport.mergeParameters(statement, parameters));
591+
return createExecutableQuery(domainType, resultType, renderer.render(statement), TemplateSupport.mergeParameters(statement, parameters));
568592
}
569593

570-
private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, String cypherStatement,
594+
private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, @Nullable Class<?> resultType, @Nullable String cypherStatement,
571595
Map<String, Object> parameters) {
572596

573-
Assert.notNull(neo4jMappingContext.getPersistentEntity(domainType), "Cannot get or create persistent entity.");
597+
BiFunction<TypeSystem, MapAccessor, ?> mappingFunction = TemplateSupport
598+
.getAndDecorateMappingFunction(neo4jMappingContext, domainType, resultType);
574599
PreparedQuery<T> preparedQuery = PreparedQuery.queryFor(domainType)
575600
.withCypherQuery(cypherStatement)
576601
.withParameters(parameters)
577-
.usingMappingFunction(neo4jMappingContext.getRequiredMappingFunctionFor(domainType))
602+
.usingMappingFunction(mappingFunction)
578603
.build();
579604

580605
return toExecutableQuery(preparedQuery);
@@ -796,15 +821,18 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
796821
public <T> ExecutableQuery<T> toExecutableQuery(Class<T> domainType,
797822
QueryFragmentsAndParameters queryFragmentsAndParameters) {
798823

799-
return createExecutableQuery(domainType, queryFragmentsAndParameters);
824+
return createExecutableQuery(domainType, null, queryFragmentsAndParameters);
800825
}
801826

802-
private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType,
803-
QueryFragmentsAndParameters queryFragmentsAndParameters) {
804827

828+
private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, Class<?> resultType,
829+
QueryFragmentsAndParameters queryFragmentsAndParameters) {
830+
831+
BiFunction<TypeSystem, MapAccessor, ?> mappingFunction = TemplateSupport
832+
.getAndDecorateMappingFunction(neo4jMappingContext, domainType, resultType);
805833
PreparedQuery<T> preparedQuery = PreparedQuery.queryFor(domainType)
806834
.withQueryFragmentsAndParameters(queryFragmentsAndParameters)
807-
.usingMappingFunction(neo4jMappingContext.getRequiredMappingFunctionFor(domainType))
835+
.usingMappingFunction(mappingFunction)
808836
.build();
809837
return toExecutableQuery(preparedQuery);
810838
}

src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java

+41-24
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.Map;
3535
import java.util.Set;
3636
import java.util.concurrent.ConcurrentHashMap;
37+
import java.util.function.BiFunction;
3738
import java.util.function.Function;
3839
import java.util.function.Predicate;
3940
import java.util.stream.Collectors;
@@ -48,6 +49,8 @@
4849
import org.neo4j.cypherdsl.core.renderer.Renderer;
4950
import org.neo4j.driver.summary.SummaryCounters;
5051
import org.neo4j.driver.types.Entity;
52+
import org.neo4j.driver.types.MapAccessor;
53+
import org.neo4j.driver.types.TypeSystem;
5154
import org.reactivestreams.Publisher;
5255
import org.springframework.beans.BeansException;
5356
import org.springframework.beans.factory.BeanClassLoaderAware;
@@ -63,6 +66,8 @@
6366
import org.springframework.data.neo4j.core.mapping.Constants;
6467
import org.springframework.data.neo4j.core.mapping.CreateRelationshipStatementHolder;
6568
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
69+
import org.springframework.data.neo4j.core.mapping.DtoInstantiatingConverter;
70+
import org.springframework.data.neo4j.core.mapping.EntityInstanceWithSource;
6671
import org.springframework.data.neo4j.core.mapping.MappingSupport;
6772
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
6873
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
@@ -166,8 +171,13 @@ public Mono<Long> count(String cypherQuery, Map<String, Object> parameters) {
166171
@Override
167172
public <T> Flux<T> findAll(Class<T> domainType) {
168173

174+
return doFindAll(domainType, null);
175+
}
176+
177+
private <T> Flux<T> doFindAll(Class<T> domainType, @Nullable Class<?> resultType) {
178+
169179
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getPersistentEntity(domainType);
170-
return createExecutableQuery(domainType, QueryFragmentsAndParameters.forFindAll(entityMetaData))
180+
return createExecutableQuery(domainType, resultType, QueryFragmentsAndParameters.forFindAll(entityMetaData))
171181
.flatMapMany(ExecutableQuery::getResults);
172182
}
173183

@@ -180,13 +190,13 @@ public <T> Flux<T> findAll(Statement statement, Class<T> domainType) {
180190
@Override
181191
public <T> Flux<T> findAll(Statement statement, Map<String, Object> parameters, Class<T> domainType) {
182192

183-
return createExecutableQuery(domainType, statement, parameters).flatMapMany(ExecutableQuery::getResults);
193+
return createExecutableQuery(domainType, null, statement, parameters).flatMapMany(ExecutableQuery::getResults);
184194
}
185195

186196
@Override
187197
public <T> Mono<T> findOne(Statement statement, Map<String, Object> parameters, Class<T> domainType) {
188198

189-
return createExecutableQuery(domainType, statement, parameters).flatMap(ExecutableQuery::getSingleResult);
199+
return createExecutableQuery(domainType, null, statement, parameters).flatMap(ExecutableQuery::getSingleResult);
190200
}
191201

192202
@Override
@@ -196,26 +206,27 @@ public <T> Flux<T> findAll(String cypherQuery, Class<T> domainType) {
196206

197207
@Override
198208
public <T> Flux<T> findAll(String cypherQuery, Map<String, Object> parameters, Class<T> domainType) {
199-
return createExecutableQuery(domainType, cypherQuery, parameters).flatMapMany(ExecutableQuery::getResults);
209+
return createExecutableQuery(domainType, null, cypherQuery, parameters).flatMapMany(ExecutableQuery::getResults);
200210
}
201211

202212
@Override
203213
public <T> Mono<T> findOne(String cypherQuery, Map<String, Object> parameters, Class<T> domainType) {
204-
return createExecutableQuery(domainType, cypherQuery, parameters).flatMap(ExecutableQuery::getSingleResult);
214+
return createExecutableQuery(domainType, null, cypherQuery, parameters).flatMap(ExecutableQuery::getSingleResult);
205215
}
206216

207217
@Override
208218
public <T> ExecutableFind<T> find(Class<T> domainType) {
209219
return new ReactiveFluentFindOperationSupport(this).find(domainType);
210220
}
211221

212-
<T, R> Flux<R> doFind(String cypherQuery, Map<String, Object> parameters, Class<T> domainType, Class<R> resultType, TemplateSupport.FetchType fetchType) {
222+
@SuppressWarnings("unchecked")
223+
<T, R> Flux<R> doFind(@Nullable String cypherQuery, @Nullable Map<String, Object> parameters, Class<T> domainType, Class<R> resultType, TemplateSupport.FetchType fetchType) {
213224

214-
Flux<T> intermediaResults = Flux.empty();
225+
Flux<T> intermediaResults = null;
215226
if (cypherQuery == null && fetchType == TemplateSupport.FetchType.ALL) {
216-
intermediaResults = findAll(domainType);
227+
intermediaResults = doFindAll(domainType, resultType);
217228
} else {
218-
Mono<ExecutableQuery<T>> executableQuery = createExecutableQuery(domainType, cypherQuery,
229+
Mono<ExecutableQuery<T>> executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
219230
parameters == null ? Collections.emptyMap() : parameters);
220231

221232
switch (fetchType) {
@@ -232,15 +243,21 @@ <T, R> Flux<R> doFind(String cypherQuery, Map<String, Object> parameters, Class<
232243
return (Flux<R>) intermediaResults;
233244
}
234245

235-
return intermediaResults.map(instance -> projectionFactory.createProjection(resultType, instance));
246+
if (resultType.isInterface()) {
247+
return intermediaResults.map(instance -> projectionFactory.createProjection(resultType, instance));
248+
}
249+
250+
DtoInstantiatingConverter converter = new DtoInstantiatingConverter(resultType, neo4jMappingContext);
251+
return (Flux<R>) intermediaResults.map(EntityInstanceWithSource.class::cast)
252+
.map(converter::convert);
236253
}
237254

238255
@Override
239256
public <T> Mono<T> findById(Object id, Class<T> domainType) {
240257

241258
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getPersistentEntity(domainType);
242259

243-
return createExecutableQuery(domainType,
260+
return createExecutableQuery(domainType, null,
244261
QueryFragmentsAndParameters.forFindById(entityMetaData,
245262
convertIdValues(entityMetaData.getRequiredIdProperty(), id)))
246263
.flatMap(ExecutableQuery::getSingleResult);
@@ -251,7 +268,7 @@ public <T> Flux<T> findAllById(Iterable<?> ids, Class<T> domainType) {
251268

252269
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getPersistentEntity(domainType);
253270

254-
return createExecutableQuery(domainType,
271+
return createExecutableQuery(domainType, null,
255272
QueryFragmentsAndParameters.forFindByAllId(entityMetaData,
256273
convertIdValues(entityMetaData.getRequiredIdProperty(), ids)))
257274
.flatMapMany(ExecutableQuery::getResults);
@@ -261,7 +278,7 @@ public <T> Flux<T> findAllById(Iterable<?> ids, Class<T> domainType) {
261278
public <T> Mono<ExecutableQuery<T>> toExecutableQuery(Class<T> domainType,
262279
QueryFragmentsAndParameters queryFragmentsAndParameters) {
263280

264-
return createExecutableQuery(domainType, queryFragmentsAndParameters);
281+
return createExecutableQuery(domainType, null, queryFragmentsAndParameters);
265282
}
266283

267284

@@ -529,31 +546,31 @@ public Mono<Void> deleteAll(Class<?> domainType) {
529546
}
530547

531548
private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType, Statement statement) {
532-
return createExecutableQuery(domainType, statement, Collections.emptyMap());
549+
return createExecutableQuery(domainType, null, statement, Collections.emptyMap());
533550
}
534551

535552
private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType, String cypherQuery) {
536-
return createExecutableQuery(domainType, cypherQuery, Collections.emptyMap());
553+
return createExecutableQuery(domainType, null, cypherQuery, Collections.emptyMap());
537554
}
538555

539-
private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType, Statement statement,
556+
private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType, @Nullable Class<?> resultType, Statement statement,
540557
Map<String, Object> parameters) {
541558

542-
return createExecutableQuery(domainType, renderer.render(statement), TemplateSupport.mergeParameters(statement, parameters));
559+
return createExecutableQuery(domainType, resultType, renderer.render(statement), TemplateSupport.mergeParameters(statement, parameters));
543560
}
544561

545-
private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType, String cypherQuery,
562+
private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType, @Nullable Class<?> resultType, @Nullable String cypherQuery,
546563
Map<String, Object> parameters) {
547564

548-
Assert.notNull(neo4jMappingContext.getPersistentEntity(domainType), "Cannot get or create persistent entity.");
565+
BiFunction<TypeSystem, MapAccessor, ?> mappingFunction = TemplateSupport
566+
.getAndDecorateMappingFunction(neo4jMappingContext, domainType, resultType);
549567
PreparedQuery<T> preparedQuery = PreparedQuery.queryFor(domainType).withCypherQuery(cypherQuery)
550568
.withParameters(parameters)
551-
.usingMappingFunction(this.neo4jMappingContext.getRequiredMappingFunctionFor(domainType)).build();
569+
.usingMappingFunction(mappingFunction).build();
552570
return this.toExecutableQuery(preparedQuery);
553571
}
554572

555-
private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType,
556-
QueryFragmentsAndParameters queryFragmentsAndParameters) {
573+
private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType, @Nullable Class<?> resultType, QueryFragmentsAndParameters queryFragmentsAndParameters) {
557574

558575
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getPersistentEntity(domainType);
559576
QueryFragments queryFragments = queryFragmentsAndParameters.getQueryFragments();
@@ -562,11 +579,11 @@ private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType,
562579
if (containsPossibleCircles && !queryFragments.isScalarValueReturn()) {
563580
return createNodesAndRelationshipsByIdStatementProvider(entityMetaData, queryFragments, queryFragmentsAndParameters.getParameters())
564581
.flatMap(finalQueryAndParameters ->
565-
createExecutableQuery(domainType, renderer.render(finalQueryAndParameters.toStatement()),
582+
createExecutableQuery(domainType, resultType, renderer.render(finalQueryAndParameters.toStatement()),
566583
finalQueryAndParameters.getParameters()));
567584
}
568585

569-
return createExecutableQuery(domainType, queryFragments.toStatement(), queryFragmentsAndParameters.getParameters());
586+
return createExecutableQuery(domainType, resultType, queryFragments.toStatement(), queryFragmentsAndParameters.getParameters());
570587
}
571588

572589
private Mono<NodesAndRelationshipsByIdStatementProvider> createNodesAndRelationshipsByIdStatementProvider(Neo4jPersistentEntity<?> entityMetaData,

0 commit comments

Comments
 (0)