@@ -341,15 +341,21 @@ private <T> T saveImpl(T instance, @Nullable List<PropertyDescriptor> includedPr
341341 .with (binderFunction )
342342 .fetchAs (Long .class ).one ();
343343
344- if (entityMetaData .hasVersionProperty () && !optionalInternalId .isPresent ()) {
345- throw new OptimisticLockingFailureException (OPTIMISTIC_LOCKING_ERROR_MESSAGE );
344+ if (!optionalInternalId .isPresent ()) {
345+ if (entityMetaData .hasVersionProperty ()) {
346+ throw new OptimisticLockingFailureException (OPTIMISTIC_LOCKING_ERROR_MESSAGE );
347+ }
348+ // defensive exception throwing
349+ throw new IllegalStateException ("Could not retrieve an internal id while saving." );
346350 }
347351
352+ Long internalId = optionalInternalId .get ();
353+
348354 PersistentPropertyAccessor <T > propertyAccessor = entityMetaData .getPropertyAccessor (entityToBeSaved );
349355 if (entityMetaData .isUsingInternalIds ()) {
350- propertyAccessor .setProperty (entityMetaData .getRequiredIdProperty (), optionalInternalId . get () );
356+ propertyAccessor .setProperty (entityMetaData .getRequiredIdProperty (), internalId );
351357 }
352- processRelations (entityMetaData , instance , propertyAccessor , isEntityNew , includeProperty );
358+ processRelations (entityMetaData , instance , internalId , propertyAccessor , isEntityNew , includeProperty );
353359
354360 return propertyAccessor .getBean ();
355361 }
@@ -581,6 +587,14 @@ private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, String
581587 * @param isParentObjectNew A flag if the parent was new
582588 * @param includeProperty A predicate telling to include a relationship property or not
583589 */
590+ private <T > T processRelations (Neo4jPersistentEntity <?> neo4jPersistentEntity , T originalInstance , Long internalId ,
591+ PersistentPropertyAccessor <?> parentPropertyAccessor ,
592+ boolean isParentObjectNew , Predicate <String > includeProperty ) {
593+
594+ return processNestedRelations (neo4jPersistentEntity , parentPropertyAccessor , isParentObjectNew ,
595+ new NestedRelationshipProcessingStateMachine (originalInstance , internalId ), includeProperty );
596+ }
597+
584598 private <T > T processRelations (Neo4jPersistentEntity <?> neo4jPersistentEntity , T originalInstance ,
585599 PersistentPropertyAccessor <?> parentPropertyAccessor ,
586600 boolean isParentObjectNew , Predicate <String > includeProperty ) {
@@ -666,22 +680,23 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Pers
666680 for (Object relatedValueToStore : relatedValuesToStore ) {
667681
668682 // here a map entry is not always anymore a dynamic association
669- Object relatedObjectBeforeCallbacks = relationshipContext .identifyAndExtractRelationshipTargetNode (relatedValueToStore );
670- Neo4jPersistentEntity <?> targetEntity = neo4jMappingContext .getPersistentEntity (relatedObjectBeforeCallbacks .getClass ());
683+ Object relatedObjectBeforeCallbacksApplied = relationshipContext .identifyAndExtractRelationshipTargetNode (relatedValueToStore );
684+ Neo4jPersistentEntity <?> targetEntity = neo4jMappingContext .getPersistentEntity (relatedObjectBeforeCallbacksApplied .getClass ());
671685
672- boolean isEntityNew = targetEntity .isNew (relatedObjectBeforeCallbacks );
686+ boolean isEntityNew = targetEntity .isNew (relatedObjectBeforeCallbacksApplied );
673687
674- Object newRelatedObject = eventSupport .maybeCallBeforeBind (relatedObjectBeforeCallbacks );
688+ Object newRelatedObject = stateMachine .hasProcessedValue (relatedObjectBeforeCallbacksApplied )
689+ ? stateMachine .getProcessedAs (relatedObjectBeforeCallbacksApplied )
690+ : eventSupport .maybeCallBeforeBind (relatedObjectBeforeCallbacksApplied );
675691
676692 Long relatedInternalId ;
677693 // No need to save values if processed
678694 if (stateMachine .hasProcessedValue (relatedValueToStore )) {
679- Object newRelatedObjectForQuery = stateMachine .getProcessedAs (newRelatedObject );
680- relatedInternalId = queryRelatedNode (newRelatedObjectForQuery , targetEntity );
695+ relatedInternalId = stateMachine .getInternalId (relatedObjectBeforeCallbacksApplied );
681696 } else {
682697 relatedInternalId = saveRelatedNode (newRelatedObject , targetEntity );
683698 }
684- stateMachine .markValueAsProcessed (relatedValueToStore );
699+ stateMachine .markValueAsProcessed (relatedValueToStore , relatedInternalId );
685700
686701 Object idValue = idProperty != null
687702 ? relationshipContext
@@ -713,8 +728,8 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Pers
713728 // if an internal id is used this must be set to link this entity in the next iteration
714729 if (targetEntity .isUsingInternalIds ()) {
715730 targetPropertyAccessor .setProperty (targetEntity .getRequiredIdProperty (), relatedInternalId );
716- stateMachine .markValueAsProcessedAs (newRelatedObject , targetPropertyAccessor .getBean ());
717731 }
732+ stateMachine .markValueAsProcessedAs (relatedObjectBeforeCallbacksApplied , targetPropertyAccessor .getBean ());
718733
719734 if (processState != ProcessState .PROCESSED_ALL_VALUES ) {
720735 processNestedRelations (targetEntity , targetPropertyAccessor , isEntityNew , stateMachine , s -> true );
@@ -726,7 +741,7 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Pers
726741 relatedValueToStore ,
727742 targetPropertyAccessor );
728743
729- relationshipHandler .handle (relatedValueToStore , relatedObjectBeforeCallbacks , potentiallyRecreatedNewRelatedObject );
744+ relationshipHandler .handle (relatedValueToStore , relatedObjectBeforeCallbacksApplied , potentiallyRecreatedNewRelatedObject );
730745 }
731746
732747 relationshipHandler .applyFinalResultToOwner (propertyAccessor );
@@ -735,22 +750,6 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Pers
735750 return (T ) propertyAccessor .getBean ();
736751 }
737752
738- private <Y > Long queryRelatedNode (Object entity , Neo4jPersistentEntity <?> targetNodeDescription ) {
739-
740- Neo4jPersistentProperty requiredIdProperty = targetNodeDescription .getRequiredIdProperty ();
741- PersistentPropertyAccessor <Object > targetPropertyAccessor = targetNodeDescription .getPropertyAccessor (entity );
742- Object idValue = targetPropertyAccessor .getProperty (requiredIdProperty );
743-
744- return neo4jClient .query (() ->
745- renderer .render (cypherGenerator .prepareMatchOf (targetNodeDescription ,
746- targetNodeDescription .getIdExpression ().isEqualTo (parameter (Constants .NAME_OF_ID )))
747- .returning (Constants .NAME_OF_INTERNAL_ID )
748- .build ())
749- )
750- .bindAll (Collections .singletonMap (Constants .NAME_OF_ID , idValue ))
751- .fetchAs (Long .class ).one ().get ();
752- }
753-
754753 private <Y > Long saveRelatedNode (Object entity , NodeDescription targetNodeDescription ) {
755754
756755 DynamicLabels dynamicLabels = determineDynamicLabels (entity , (Neo4jPersistentEntity ) targetNodeDescription );
0 commit comments