Skip to content

Commit f9267d3

Browse files
michael-simonsmeistermeier
authored andcommitted
GH-2148 - Create new objects/collection on save.
If needed create new collections and object instances on save. This can be the case if the underlying objects are immutable.
1 parent 9944cf8 commit f9267d3

29 files changed

+4112
-285
lines changed

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

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,10 @@ private <T> T saveImpl(T instance) {
267267
PersistentPropertyAccessor<T> propertyAccessor = entityMetaData.getPropertyAccessor(entityToBeSaved);
268268
if (entityMetaData.isUsingInternalIds()) {
269269
propertyAccessor.setProperty(entityMetaData.getRequiredIdProperty(), optionalInternalId.get());
270-
entityToBeSaved = propertyAccessor.getBean();
271270
}
272-
return processRelations(entityMetaData, entityToBeSaved, isEntityNew, instance);
271+
processRelations(entityMetaData, instance, propertyAccessor, isEntityNew);
272+
273+
return propertyAccessor.getBean();
273274
}
274275

275276
private <T> DynamicLabels determineDynamicLabels(T entityToBeSaved, Neo4jPersistentEntity<?> entityMetaData) {
@@ -318,36 +319,41 @@ public <T> List<T> saveAll(Iterable<T> instances) {
318319
return entities.stream().map(e -> saveImpl(e)).collect(Collectors.toList());
319320
}
320321

321-
// we need to determine the `isNew` state of the entities before calling the id generator
322-
List<Boolean> isNewIndicator = entities.stream().map(entity ->
323-
neo4jMappingContext.getPersistentEntity(entity.getClass()).isNew(entity)
324-
).collect(Collectors.toList());
322+
class Tuple3<T> {
323+
T t1;
324+
boolean t2;
325+
T t3;
326+
327+
Tuple3(T t1, boolean t2, T t3) {
328+
this.t1 = t1;
329+
this.t2 = t2;
330+
this.t3 = t3;
331+
}
332+
}
325333

326-
List<T> entitiesToBeSaved = entities.stream().map(eventSupport::maybeCallBeforeBind)
334+
List<Tuple3<T>> entitiesToBeSaved = entities.stream()
335+
.map(e -> new Tuple3<>(e, neo4jMappingContext.getPersistentEntity(e.getClass()).isNew(e), eventSupport.maybeCallBeforeBind(e)))
327336
.collect(Collectors.toList());
328337

329338
// Save roots
330339
Function<T, Map<String, Object>> binderFunction = neo4jMappingContext.getRequiredBinderFunctionFor(domainClass);
331-
List<Map<String, Object>> entityList = entitiesToBeSaved.stream().map(binderFunction)
340+
List<Map<String, Object>> entityList = entitiesToBeSaved.stream().map(h -> h.t3).map(binderFunction)
332341
.collect(Collectors.toList());
333342
ResultSummary resultSummary = neo4jClient
334343
.query(() -> renderer.render(cypherGenerator.prepareSaveOfMultipleInstancesOf(entityMetaData)))
335344
.bind(entityList).to(Constants.NAME_OF_ENTITY_LIST_PARAM).run();
336345

337-
// Save related
338-
entitiesToBeSaved.forEach(entityToBeSaved -> {
339-
int positionInList = entitiesToBeSaved.indexOf(entityToBeSaved);
340-
processRelations(entityMetaData, entityToBeSaved, isNewIndicator.get(positionInList),
341-
entities.get(positionInList));
342-
});
343-
344346
SummaryCounters counters = resultSummary.counters();
345347
log.debug(() -> String.format(
346348
"Created %d and deleted %d nodes, created %d and deleted %d relationships and set %d properties.",
347349
counters.nodesCreated(), counters.nodesDeleted(), counters.relationshipsCreated(),
348350
counters.relationshipsDeleted(), counters.propertiesSet()));
349351

350-
return entitiesToBeSaved;
352+
// Save related
353+
return entitiesToBeSaved.stream().map(t -> {
354+
PersistentPropertyAccessor<T> propertyAccessor = entityMetaData.getPropertyAccessor(t.t3);
355+
return processRelations(entityMetaData, t.t1, propertyAccessor, t.t2);
356+
}).collect(Collectors.toList());
351357
}
352358

353359
@Override
@@ -448,27 +454,37 @@ private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, String
448454
return toExecutableQuery(preparedQuery);
449455
}
450456

451-
private <T> T processRelations(Neo4jPersistentEntity<?> neo4jPersistentEntity, Object parentObject,
452-
boolean isParentObjectNew, Object parentEntity) {
457+
/**
458+
* Starts of processing of the relationships.
459+
*
460+
* @param neo4jPersistentEntity The description of the instance to save
461+
* @param originalInstance The original parent instance. It is paramount to pass in the original instance (prior
462+
* to generating the id and prior to eventually create new instances via the property accessor,
463+
* so that we can reliable stop traversing relationships.
464+
* @param parentPropertyAccessor The property accessor of the parent, to modify the relationships
465+
* @param isParentObjectNew A flag if the parent was new
466+
*/
467+
private <T> T processRelations(Neo4jPersistentEntity<?> neo4jPersistentEntity, T originalInstance,
468+
PersistentPropertyAccessor<?> parentPropertyAccessor,
469+
boolean isParentObjectNew) {
453470

454-
return processNestedRelations(neo4jPersistentEntity, parentObject, isParentObjectNew,
455-
new NestedRelationshipProcessingStateMachine(parentEntity));
471+
return processNestedRelations(neo4jPersistentEntity, parentPropertyAccessor, isParentObjectNew,
472+
new NestedRelationshipProcessingStateMachine(originalInstance));
456473
}
457474

458-
private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Object parentObject,
475+
private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, PersistentPropertyAccessor<?> propertyAccessor,
459476
boolean isParentObjectNew, NestedRelationshipProcessingStateMachine stateMachine) {
460477

461-
PersistentPropertyAccessor<?> propertyAccessor = sourceEntity.getPropertyAccessor(parentObject);
462478
Object fromId = propertyAccessor.getProperty(sourceEntity.getRequiredIdProperty());
463479

464480
sourceEntity.doWithAssociations((AssociationHandler<Neo4jPersistentProperty>) association -> {
465481

466482
// create context to bundle parameters
467-
NestedRelationshipContext relationshipContext = NestedRelationshipContext.of(association, propertyAccessor,
468-
sourceEntity);
483+
NestedRelationshipContext relationshipContext = NestedRelationshipContext.of(association, propertyAccessor, sourceEntity);
469484

485+
Object rawValue = relationshipContext.getValue();
470486
Collection<?> relatedValuesToStore = MappingSupport.unifyRelationshipValue(relationshipContext.getInverse(),
471-
relationshipContext.getValue());
487+
rawValue);
472488

473489
RelationshipDescription relationshipDescription = relationshipContext.getRelationship();
474490
RelationshipDescription relationshipDescriptionObverse = relationshipDescription.getRelationshipObverse();
@@ -523,22 +539,26 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Obje
523539

524540
stateMachine.markRelationshipAsProcessed(fromId, relationshipDescription);
525541

542+
Neo4jPersistentProperty relationshipProperty = association.getInverse();
543+
544+
RelationshipHandler relationshipHandler = RelationshipHandler.forProperty(relationshipProperty, rawValue);
545+
526546
for (Object relatedValueToStore : relatedValuesToStore) {
527547

528548
// here a map entry is not always anymore a dynamic association
529-
Object relatedNode = relationshipContext.identifyAndExtractRelationshipTargetNode(relatedValueToStore);
530-
Neo4jPersistentEntity<?> targetEntity = neo4jMappingContext.getPersistentEntity(relatedNode.getClass());
549+
Object relatedObjectBeforeCallbacks = relationshipContext.identifyAndExtractRelationshipTargetNode(relatedValueToStore);
550+
Neo4jPersistentEntity<?> targetEntity = neo4jMappingContext.getPersistentEntity(relatedObjectBeforeCallbacks.getClass());
531551

532-
boolean isEntityNew = targetEntity.isNew(relatedNode);
552+
boolean isEntityNew = targetEntity.isNew(relatedObjectBeforeCallbacks);
533553

534-
relatedNode = eventSupport.maybeCallBeforeBind(relatedNode);
554+
Object newRelatedObject = eventSupport.maybeCallBeforeBind(relatedObjectBeforeCallbacks);
535555

536556
Long relatedInternalId;
537557
// No need to save values if processed
538558
if (stateMachine.hasProcessedValue(relatedValueToStore)) {
539-
relatedInternalId = queryRelatedNode(relatedNode, targetEntity);
559+
relatedInternalId = queryRelatedNode(newRelatedObject, targetEntity);
540560
} else {
541-
relatedInternalId = saveRelatedNode(relatedNode, relationshipContext.getAssociationTargetType(),
561+
relatedInternalId = saveRelatedNode(newRelatedObject, relationshipContext.getAssociationTargetType(),
542562
targetEntity);
543563
}
544564
stateMachine.markValueAsProcessed(relatedValueToStore);
@@ -569,16 +589,26 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Obje
569589
.setProperty(idProperty, relationshipInternalId.get());
570590
}
571591

572-
PersistentPropertyAccessor<?> targetPropertyAccessor = targetEntity.getPropertyAccessor(relatedNode);
573-
// if an internal id is used this must get set to link this entity in the next iteration
592+
PersistentPropertyAccessor<?> targetPropertyAccessor = targetEntity.getPropertyAccessor(newRelatedObject);
593+
// if an internal id is used this must be set to link this entity in the next iteration
574594
if (targetEntity.isUsingInternalIds()) {
575595
targetPropertyAccessor.setProperty(targetEntity.getRequiredIdProperty(), relatedInternalId);
576596
}
597+
577598
if (processState != ProcessState.PROCESSED_ALL_VALUES) {
578-
processNestedRelations(targetEntity, targetPropertyAccessor.getBean(), isEntityNew, stateMachine);
599+
processNestedRelations(targetEntity, targetPropertyAccessor, isEntityNew, stateMachine);
579600
}
601+
602+
Object potentiallyRecreatedNewRelatedObject = MappingSupport.getRelationshipOrRelationshipPropertiesObject(neo4jMappingContext,
603+
relationshipDescription.hasRelationshipProperties(),
604+
relationshipProperty.isDynamicAssociation(),
605+
relatedValueToStore,
606+
targetPropertyAccessor);
607+
608+
relationshipHandler.handle(relatedValueToStore, relatedObjectBeforeCallbacks, potentiallyRecreatedNewRelatedObject);
580609
}
581610

611+
relationshipHandler.applyFinalResultToOwner(propertyAccessor);
582612
});
583613

584614
return (T) propertyAccessor.getBean();

0 commit comments

Comments
 (0)