3333import java .util .function .Function ;
3434import java .util .function .Supplier ;
3535import java .util .stream .Collectors ;
36+ import java .util .stream .Stream ;
3637
3738import org .apache .commons .logging .LogFactory ;
3839import org .apiguardian .api .API ;
8788import org .springframework .data .neo4j .core .mapping .SpringDataCypherDsl ;
8889import org .springframework .data .neo4j .core .mapping .callback .EventSupport ;
8990import org .springframework .data .neo4j .core .schema .TargetNode ;
90- import org .springframework .data .neo4j .core .support .UserDefinedChangeEvaluator ;
91+ import org .springframework .data .neo4j .core .support .NeedsUpdateEvaluator ;
9192import org .springframework .data .neo4j .core .transaction .Neo4jTransactionManager ;
9293import org .springframework .data .neo4j .repository .NoResultException ;
9394import org .springframework .data .neo4j .repository .query .QueryFragments ;
@@ -157,7 +158,8 @@ public boolean isReadOnly() {
157158
158159 @ Nullable
159160 private TransactionTemplate transactionTemplateReadOnly ;
160- private final Map <Class , UserDefinedChangeEvaluator > userDefinedChangeEvaluators = new HashMap <>();
161+
162+ private final Map <Class , NeedsUpdateEvaluator > needsUpdateEvaluators = new HashMap <>();
161163
162164 public Neo4jTemplate (Neo4jClient neo4jClient ) {
163165 this (neo4jClient , new Neo4jMappingContext ());
@@ -454,15 +456,19 @@ public <T> T save(T instance) {
454456 private <T > T saveImpl (T instance , @ Nullable Collection <PropertyFilter .ProjectedPath > includedProperties ,
455457 @ Nullable NestedRelationshipProcessingStateMachine stateMachine ) {
456458
457- if ((stateMachine != null && stateMachine .hasProcessedValue (instance ))
458- || (this .userDefinedChangeEvaluators .containsKey (instance .getClass ()) && !this .userDefinedChangeEvaluators .get (instance .getClass ()).needsUpdate (instance ))) {
459+ if (stateMachine != null && stateMachine .hasProcessedValue (instance )) {
459460 return instance ;
460461 }
461462
462463 Neo4jPersistentEntity <?> entityMetaData = this .neo4jMappingContext
463464 .getRequiredPersistentEntity (instance .getClass ());
464465 boolean isEntityNew = entityMetaData .isNew (instance );
465466
467+ if (!isEntityNew && this .needsUpdateEvaluators .containsKey (instance .getClass ())
468+ && !this .needsUpdateEvaluators .get (instance .getClass ()).needsUpdate (instance )) {
469+ return instance ;
470+ }
471+
466472 T entityToBeSaved = this .eventSupport .maybeCallBeforeBind (instance );
467473
468474 DynamicLabels dynamicLabels = determineDynamicLabels (entityToBeSaved , entityMetaData );
@@ -612,9 +618,16 @@ class Tuple3<T> {
612618 }
613619
614620 List <Tuple3 <T >> entitiesToBeSaved = entities .stream ()
621+ .filter (e -> entityMetaData .isNew (e ) || !this .needsUpdateEvaluators .containsKey (e .getClass ())
622+ || this .needsUpdateEvaluators .get (e .getClass ()).needsUpdate (e ))
615623 .map (e -> new Tuple3 <>(e , entityMetaData .isNew (e ), this .eventSupport .maybeCallBeforeBind (e )))
616624 .collect (Collectors .toList ());
617625
626+ List <T > unprocessedEntities = entities .stream ()
627+ .filter (e -> !entityMetaData .isNew (e ) && (this .needsUpdateEvaluators .containsKey (e .getClass ())
628+ && !this .needsUpdateEvaluators .get (e .getClass ()).needsUpdate (e )))
629+ .toList ();
630+
618631 // Save roots
619632 @ SuppressWarnings ("unchecked" ) // We can safely assume here that we have a
620633 // humongous collection with only one single type
@@ -640,7 +653,7 @@ class Tuple3<T> {
640653
641654 // Save related
642655 var stateMachine = new NestedRelationshipProcessingStateMachine (this .neo4jMappingContext , null , null );
643- return entitiesToBeSaved .stream ().map (t -> {
656+ return Stream . of ( entitiesToBeSaved .stream ().map (t -> {
644657 PersistentPropertyAccessor <T > propertyAccessor = entityMetaData .getPropertyAccessor (t .modifiedInstance );
645658 Neo4jPersistentProperty idProperty = entityMetaData .getRequiredIdProperty ();
646659 Object id = TemplateSupport .convertIdValues (this .neo4jMappingContext , idProperty ,
@@ -652,7 +665,7 @@ class Tuple3<T> {
652665 ((includedProperties != null && !includedProperties .isEmpty ()) || includeProperty != null )
653666 ? pps : includedPropertiesByClass .get (t .modifiedInstance .getClass ()),
654667 entityMetaData ));
655- }).collect (Collectors .toList ());
668+ }).collect (Collectors .toList ()), unprocessedEntities ). flatMap ( Collection :: stream ). toList () ;
656669 }
657670
658671 @ Override
@@ -984,18 +997,18 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity,
984997 var skipUpdateOfEntity = !isNewEntity ;
985998 if (relatedValueToStore instanceof MappingSupport .RelationshipPropertiesWithEntityHolder rpweh ) {
986999 var relatedEntity = rpweh .getRelatedEntity ();
987- skipUpdateOfEntity &= this .userDefinedChangeEvaluators .containsKey (relatedEntity .getClass ())
988- && !this .userDefinedChangeEvaluators .get (relatedEntity .getClass ()).needsUpdate (relatedEntity );
1000+ skipUpdateOfEntity &= this .needsUpdateEvaluators .containsKey (relatedEntity .getClass ())
1001+ && !this .needsUpdateEvaluators .get (relatedEntity .getClass ()).needsUpdate (relatedEntity );
9891002 }
9901003 else {
991- skipUpdateOfEntity &= this .userDefinedChangeEvaluators .containsKey (relatedValueToStore .getClass ())
992- && !this .userDefinedChangeEvaluators .get (relatedValueToStore .getClass ()).needsUpdate (relatedValueToStore );
1004+ skipUpdateOfEntity &= this .needsUpdateEvaluators .containsKey (relatedValueToStore .getClass ())
1005+ && !this .needsUpdateEvaluators .get (relatedValueToStore .getClass ())
1006+ .needsUpdate (relatedValueToStore );
9931007 }
9941008 Object newRelatedObject = stateMachine .hasProcessedValue (relatedObjectBeforeCallbacksApplied )
9951009 ? stateMachine .getProcessedAs (relatedObjectBeforeCallbacksApplied )
996- : skipUpdateOfEntity
997- ? relatedObjectBeforeCallbacksApplied
998- : this .eventSupport .maybeCallBeforeBind (relatedObjectBeforeCallbacksApplied );
1010+ : skipUpdateOfEntity ? relatedObjectBeforeCallbacksApplied
1011+ : this .eventSupport .maybeCallBeforeBind (relatedObjectBeforeCallbacksApplied );
9991012
10001013 Object relatedInternalId ;
10011014 Entity savedEntity = null ;
@@ -1004,7 +1017,7 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity,
10041017 relatedInternalId = stateMachine .getObjectId (relatedValueToStore );
10051018 }
10061019 else {
1007- if (( isNewEntity || relationshipDescription .cascadeUpdates ()) && !skipUpdateOfEntity ) {
1020+ if (isNewEntity || ( relationshipDescription .cascadeUpdates () && !skipUpdateOfEntity ) ) {
10081021 savedEntity = saveRelatedNode (newRelatedObject , targetEntity , includeProperty ,
10091022 currentPropertyPath );
10101023 }
@@ -1341,8 +1354,9 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
13411354 }
13421355 }
13431356 setTransactionManager (transactionManager );
1344- this .userDefinedChangeEvaluators .putAll (beanFactory .getBeanProvider (UserDefinedChangeEvaluator .class ).stream ()
1345- .collect (Collectors .toMap (e -> e .getEvaluatingClass (), e -> e )));
1357+ this .needsUpdateEvaluators .putAll (beanFactory .getBeanProvider (NeedsUpdateEvaluator .class )
1358+ .stream ()
1359+ .collect (Collectors .toMap (e -> e .getEvaluatingClass (), e -> e )));
13461360 }
13471361
13481362 // only used for the CDI configuration
0 commit comments