@@ -770,6 +770,11 @@ private <T> T processNestedRelations(
770770 Neo4jPersistentProperty relationshipProperty = association .getInverse ();
771771
772772 RelationshipHandler relationshipHandler = RelationshipHandler .forProperty (relationshipProperty , rawValue );
773+ List <Object > plainRelationshipRows = new ArrayList <>();
774+ List <Map <String , Object >> relationshipPropertiesRows = new ArrayList <>();
775+ List <Map <String , Object >> newRelationshipPropertiesRows = new ArrayList <>();
776+ List <Object > updateRelatedValuesToStore = new ArrayList <>();
777+ List <Object > newRelatedValuesToStore = new ArrayList <>();
773778
774779 for (Object relatedValueToStore : relatedValuesToStore ) {
775780
@@ -818,28 +823,60 @@ private <T> T processNestedRelations(
818823
819824 Object idValue = idProperty != null
820825 ? relationshipContext
821- .getRelationshipPropertiesPropertyAccessor (relatedValueToStore ).getProperty (idProperty )
826+ .getRelationshipPropertiesPropertyAccessor (relatedValueToStore ).getProperty (idProperty )
822827 : null ;
823828
829+ Map <String , Object > properties = new HashMap <>();
830+ properties .put (Constants .FROM_ID_PARAMETER_NAME , convertIdValues (sourceEntity .getRequiredIdProperty (), fromId ));
831+ properties .put (Constants .TO_ID_PARAMETER_NAME , relatedInternalId );
832+ properties .put (Constants .NAME_OF_KNOWN_RELATIONSHIP_PARAM , idValue );
824833 boolean isNewRelationship = idValue == null ;
825-
826- CreateRelationshipStatementHolder statementHolder = neo4jMappingContext .createStatement (
827- sourceEntity , relationshipContext , relatedValueToStore , isNewRelationship );
828-
829- Optional <Long > relationshipInternalId = neo4jClient .query (renderer .render (statementHolder .getStatement ()))
830- .bind (convertIdValues (sourceEntity .getRequiredIdProperty (), fromId )) //
831- .to (Constants .FROM_ID_PARAMETER_NAME ) //
832- .bind (relatedInternalId ) //
833- .to (Constants .TO_ID_PARAMETER_NAME ) //
834- .bind (idValue ) //
835- .to (Constants .NAME_OF_KNOWN_RELATIONSHIP_PARAM ) //
836- .bindAll (statementHolder .getProperties ())
837- .fetchAs (Long .class ).one ();
838-
839- if (idProperty != null && isNewRelationship ) {
840- relationshipContext
841- .getRelationshipPropertiesPropertyAccessor (relatedValueToStore )
842- .setProperty (idProperty , relationshipInternalId .get ());
834+ if (relationshipDescription .isDynamic ()) {
835+ // create new dynamic relationship properties
836+ if (relationshipDescription .hasRelationshipProperties () && isNewRelationship && idProperty != null ) {
837+ CreateRelationshipStatementHolder statementHolder = neo4jMappingContext .createStatementForSingleRelationship (
838+ sourceEntity , relationshipDescription , relatedValueToStore , true );
839+
840+ List <Object > row = Collections .singletonList (properties );
841+ statementHolder = statementHolder .addProperty (Constants .NAME_OF_RELATIONSHIP_LIST_PARAM , row );
842+ Optional <Long > relationshipInternalId = neo4jClient .query (renderer .render (statementHolder .getStatement ()))
843+ .bind (convertIdValues (sourceEntity .getRequiredIdProperty (), fromId )) //
844+ .to (Constants .FROM_ID_PARAMETER_NAME ) //
845+ .bind (relatedInternalId ) //
846+ .to (Constants .TO_ID_PARAMETER_NAME ) //
847+ .bind (idValue ) // always null
848+ .to (Constants .NAME_OF_KNOWN_RELATIONSHIP_PARAM ) //
849+ .bindAll (statementHolder .getProperties ())
850+ .fetchAs (Long .class ).one ();
851+ assignIdToRelationshipProperties (relationshipContext , relatedValueToStore , idProperty , relationshipInternalId .get ());
852+ } else { // plain (new or to update) dynamic relationship or dynamic relationships with properties to update
853+ CreateRelationshipStatementHolder statementHolder = neo4jMappingContext .createStatementForSingleRelationship (
854+ sourceEntity , relationshipDescription , relatedValueToStore , false );
855+
856+ List <Object > row = Collections .singletonList (properties );
857+ statementHolder = statementHolder .addProperty (Constants .NAME_OF_RELATIONSHIP_LIST_PARAM , row );
858+ neo4jClient .query (renderer .render (statementHolder .getStatement ()))
859+ .bind (convertIdValues (sourceEntity .getRequiredIdProperty (), fromId )) //
860+ .to (Constants .FROM_ID_PARAMETER_NAME ) //
861+ .bind (relatedInternalId ) //
862+ .to (Constants .TO_ID_PARAMETER_NAME ) //
863+ .bind (idValue )
864+ .to (Constants .NAME_OF_KNOWN_RELATIONSHIP_PARAM ) //
865+ .bindAll (statementHolder .getProperties ())
866+ .run ();
867+ }
868+ } else if (relationshipDescription .hasRelationshipProperties () && isNewRelationship && idProperty != null ) {
869+ newRelationshipPropertiesRows .add (properties );
870+ newRelatedValuesToStore .add (relatedValueToStore );
871+ } else if (relationshipDescription .hasRelationshipProperties ()) {
872+ neo4jMappingContext .getEntityConverter ().write (
873+ ((MappingSupport .RelationshipPropertiesWithEntityHolder ) relatedValueToStore ).getRelationshipProperties (),
874+ properties );
875+
876+ relationshipPropertiesRows .add (properties );
877+ } else {
878+ // non-dynamic relationship or relationship with properties
879+ plainRelationshipRows .add (properties );
843880 }
844881
845882 if (processState != ProcessState .PROCESSED_ALL_VALUES ) {
@@ -854,6 +891,37 @@ private <T> T processNestedRelations(
854891
855892 relationshipHandler .handle (relatedValueToStore , relatedObjectBeforeCallbacksApplied , potentiallyRecreatedNewRelatedObject );
856893 }
894+ // batch operations
895+ if (!(relationshipDescription .hasRelationshipProperties () || relationshipDescription .isDynamic () || plainRelationshipRows .isEmpty ())) {
896+ CreateRelationshipStatementHolder statementHolder = neo4jMappingContext .createStatementForImperativeSimpleRelationshipBatch (
897+ sourceEntity , relationshipDescription , plainRelationshipRows );
898+ statementHolder = statementHolder .addProperty (Constants .NAME_OF_RELATIONSHIP_LIST_PARAM , plainRelationshipRows );
899+ neo4jClient .query (renderer .render (statementHolder .getStatement ()))
900+ .bindAll (statementHolder .getProperties ())
901+ .run ();
902+ } else if (relationshipDescription .hasRelationshipProperties ()) {
903+ if (!relationshipPropertiesRows .isEmpty ()) {
904+ CreateRelationshipStatementHolder statementHolder = neo4jMappingContext .createStatementForImperativeRelationshipsWithPropertiesBatch (false ,
905+ sourceEntity , relationshipDescription , updateRelatedValuesToStore , relationshipPropertiesRows );
906+ statementHolder = statementHolder .addProperty (Constants .NAME_OF_RELATIONSHIP_LIST_PARAM , relationshipPropertiesRows );
907+
908+ neo4jClient .query (renderer .render (statementHolder .getStatement ()))
909+ .bindAll (statementHolder .getProperties ())
910+ .run ();
911+ } else if (!newRelatedValuesToStore .isEmpty ()) {
912+ CreateRelationshipStatementHolder statementHolder = neo4jMappingContext .createStatementForImperativeRelationshipsWithPropertiesBatch (true ,
913+ sourceEntity , relationshipDescription , newRelatedValuesToStore , newRelationshipPropertiesRows );
914+
915+ List <Long > all = new ArrayList <>(neo4jClient .query (renderer .render (statementHolder .getStatement ()))
916+ .bindAll (statementHolder .getProperties ())
917+ .fetchAs (Long .class ).all ());
918+ // assign new ids
919+ for (int i = 0 ; i < all .size (); i ++) {
920+ Long aLong = all .get (i );
921+ assignIdToRelationshipProperties (relationshipContext , newRelatedValuesToStore .get (i ), idProperty , aLong );
922+ }
923+ }
924+ }
857925
858926 relationshipHandler .applyFinalResultToOwner (propertyAccessor );
859927 });
@@ -863,6 +931,12 @@ private <T> T processNestedRelations(
863931 return finalSubgraphRoot ;
864932 }
865933
934+ private void assignIdToRelationshipProperties (NestedRelationshipContext relationshipContext , Object relatedValueToStore , Neo4jPersistentProperty idProperty , Long relationshipInternalId ) {
935+ relationshipContext
936+ .getRelationshipPropertiesPropertyAccessor (relatedValueToStore )
937+ .setProperty (idProperty , relationshipInternalId );
938+ }
939+
866940 private Entity saveRelatedNode (Object entity , NodeDescription <?> targetNodeDescription , PropertyFilter includeProperty , PropertyFilter .RelaxedPropertyPath currentPropertyPath ) {
867941
868942 DynamicLabels dynamicLabels = determineDynamicLabels (entity , (Neo4jPersistentEntity <?>) targetNodeDescription );
0 commit comments