@@ -214,33 +214,52 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
214
214
private <ET > ET map (MapAccessor queryResult , Neo4jPersistentEntity <ET > nodeDescription , KnownObjects knownObjects ,
215
215
@ Nullable Object lastMappedEntity ) {
216
216
217
- List <String > allLabels = getLabels (queryResult );
218
- NodeDescriptionAndLabels nodeDescriptionAndLabels = nodeDescriptionStore
219
- .deriveConcreteNodeDescription (nodeDescription , allLabels );
220
- Neo4jPersistentEntity <ET > concreteNodeDescription = (Neo4jPersistentEntity <ET >) nodeDescriptionAndLabels
221
- .getNodeDescription ();
222
-
223
- Collection <RelationshipDescription > relationships = concreteNodeDescription .getRelationships ();
224
-
225
- ET instance = instantiate (concreteNodeDescription , queryResult , knownObjects , relationships ,
226
- nodeDescriptionAndLabels .getDynamicLabels ());
227
-
228
- PersistentPropertyAccessor <ET > propertyAccessor = concreteNodeDescription .getPropertyAccessor (instance );
229
-
230
- if (concreteNodeDescription .requiresPropertyPopulation ()) {
231
-
232
- // Fill simple properties
233
- Predicate <Neo4jPersistentProperty > isConstructorParameter = concreteNodeDescription
234
- .getPersistenceConstructor ()::isConstructorParameter ;
235
- PropertyHandler <Neo4jPersistentProperty > handler = populateFrom (queryResult , knownObjects , propertyAccessor ,
236
- isConstructorParameter , nodeDescriptionAndLabels .getDynamicLabels (), lastMappedEntity );
237
- concreteNodeDescription .doWithProperties (handler );
217
+ // if the given result does not contain an identifier to the mapped object cannot get temporarily saved
218
+ Long internalId = queryResult .get (Constants .NAME_OF_INTERNAL_ID ).isNull ()
219
+ ? null
220
+ : queryResult instanceof Node
221
+ ? ((Node ) queryResult ).id ()
222
+ : queryResult .get (Constants .NAME_OF_INTERNAL_ID ).asLong ();
223
+
224
+ Supplier <Object > mappedObjectSupplier = () -> {
225
+
226
+ List <String > allLabels = getLabels (queryResult );
227
+ NodeDescriptionAndLabels nodeDescriptionAndLabels = nodeDescriptionStore
228
+ .deriveConcreteNodeDescription (nodeDescription , allLabels );
229
+ Neo4jPersistentEntity <ET > concreteNodeDescription = (Neo4jPersistentEntity <ET >) nodeDescriptionAndLabels
230
+ .getNodeDescription ();
231
+
232
+ Collection <RelationshipDescription > relationships = concreteNodeDescription .getRelationships ();
233
+
234
+ ET instance = instantiate (concreteNodeDescription , queryResult , knownObjects , relationships ,
235
+ nodeDescriptionAndLabels .getDynamicLabels ());
236
+
237
+ PersistentPropertyAccessor <ET > propertyAccessor = concreteNodeDescription .getPropertyAccessor (instance );
238
+
239
+ if (concreteNodeDescription .requiresPropertyPopulation ()) {
240
+
241
+ // Fill simple properties
242
+ Predicate <Neo4jPersistentProperty > isConstructorParameter = concreteNodeDescription
243
+ .getPersistenceConstructor ()::isConstructorParameter ;
244
+ PropertyHandler <Neo4jPersistentProperty > handler = populateFrom (queryResult , propertyAccessor ,
245
+ isConstructorParameter , nodeDescriptionAndLabels .getDynamicLabels (), lastMappedEntity );
246
+ concreteNodeDescription .doWithProperties (handler );
247
+
248
+ // in a cyclic graph / with bidirectional relationships, we could end up in a state in which we
249
+ // reference the start again. Because it is getting still constructed, it won't be in the knownObjects
250
+ // store unless we temporarily put it there.
251
+ knownObjects .storeObject (internalId , instance );
252
+ // Fill associations
253
+ concreteNodeDescription .doWithAssociations (
254
+ populateFrom (queryResult , propertyAccessor , isConstructorParameter , relationships , knownObjects ));
255
+ }
256
+ ET bean = propertyAccessor .getBean ();
238
257
239
- // Fill associations
240
- concreteNodeDescription . doWithAssociations (
241
- populateFrom ( queryResult , propertyAccessor , isConstructorParameter , relationships , knownObjects )) ;
242
- }
243
- return propertyAccessor . getBean ( );
258
+ // save final state of the bean
259
+ knownObjects . storeObject ( internalId , bean );
260
+ return bean ;
261
+ };
262
+ return ( ET ) knownObjects . getObject ( internalId ). orElseGet ( mappedObjectSupplier );
244
263
}
245
264
246
265
/**
@@ -283,7 +302,7 @@ public Object getParameterValue(PreferredConstructor.Parameter parameter) {
283
302
return INSTANTIATORS .getInstantiatorFor (nodeDescription ).createInstance (nodeDescription , parameterValueProvider );
284
303
}
285
304
286
- private PropertyHandler <Neo4jPersistentProperty > populateFrom (MapAccessor queryResult , KnownObjects knownObjects ,
305
+ private PropertyHandler <Neo4jPersistentProperty > populateFrom (MapAccessor queryResult ,
287
306
PersistentPropertyAccessor <?> propertyAccessor , Predicate <Neo4jPersistentProperty > isConstructorParameter ,
288
307
Collection <String > surplusLabels , Object targetNode ) {
289
308
return property -> {
@@ -398,7 +417,7 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
398
417
399
418
for (Relationship possibleRelationship : allMatchingTypeRelationshipsInResult ) {
400
419
if (targetIdSelector .apply (possibleRelationship ) == nodeId ) {
401
- Object mappedObject = knownObjects . computeIfAbsent ( nodeId , () -> map (possibleValueNode , concreteTargetNodeDescription , knownObjects ) );
420
+ Object mappedObject = map (possibleValueNode , concreteTargetNodeDescription , knownObjects );
402
421
if (relationshipDescription .hasRelationshipProperties ()) {
403
422
404
423
Object relationshipProperties = map (possibleRelationship ,
@@ -417,10 +436,7 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
417
436
} else {
418
437
for (Value relatedEntity : list .asList (Function .identity ())) {
419
438
420
- Long internalIdValue = relatedEntity .get (Constants .NAME_OF_INTERNAL_ID ).asLong ();
421
-
422
- Object valueEntry = knownObjects .computeIfAbsent (internalIdValue ,
423
- () -> map (relatedEntity , concreteTargetNodeDescription , knownObjects ));
439
+ Object valueEntry = map (relatedEntity , concreteTargetNodeDescription , knownObjects );
424
440
425
441
if (relationshipDescription .hasRelationshipProperties ()) {
426
442
Relationship relatedEntityRelationship = relatedEntity .get (RelationshipDescription .NAME_OF_RELATIONSHIP )
@@ -476,40 +492,36 @@ static class KnownObjects {
476
492
477
493
private final Map <Long , Object > internalIdStore = new HashMap <>();
478
494
479
- Object computeIfAbsent (Long internalId , Supplier <Object > entitySupplier ) {
480
- Object knownEntity = getObject (internalId );
481
-
482
- // if it is not in the store, it has to get re-computed also for the internalIdStore
483
- if (knownEntity != null ) {
484
- return knownEntity ;
495
+ private void storeObject (@ Nullable Long internalId , Object object ) {
496
+ if (internalId == null ) {
497
+ return ;
485
498
}
486
-
487
499
try {
488
500
write .lock ();
489
- Object computedEntity = entitySupplier .get ();
490
- internalIdStore .put (internalId , computedEntity );
491
- return computedEntity ;
501
+ internalIdStore .put (internalId , object );
492
502
} finally {
493
503
write .unlock ();
494
504
}
495
505
}
496
506
497
- @ Nullable
498
- private Object getObject (Long internalId ) {
507
+ private Optional <Object > getObject (@ Nullable Long internalId ) {
508
+ if (internalId == null ) {
509
+ return Optional .empty ();
510
+ }
499
511
try {
500
512
501
513
read .lock ();
502
514
503
515
Object knownEntity = internalIdStore .get (internalId );
504
516
505
517
if (knownEntity != null ) {
506
- return knownEntity ;
518
+ return Optional . of ( knownEntity ) ;
507
519
}
508
520
509
521
} finally {
510
522
read .unlock ();
511
523
}
512
- return null ;
524
+ return Optional . empty () ;
513
525
}
514
526
}
515
527
}
0 commit comments