cancel
Showing results for 
Search instead for 
Did you mean: 

Neo4J OGM - createOrUpdate method returning duplicates when updating RelationshipEntities

Good evening,

I'm currently working on an API controller used to edit some node entities and its relationships, the node update is working well but when I update the RelationshipEntities inside the NodeEntity: Set<ContextRelation>, its returning the NodeEntity, RelationshipEntities and duplicated RelationshipEntities (they got the same ID and have both the updated values).

Technologies used:

  • Neo4J 4.0-rc1
  • Neo4J-OGM 3.2.3 (bolt driver)
  • Neo4J driver 4.0.0
  • Javalin

Here is the final JSON example:

{
    "data": {
        "id": 5,
        "contexts": [
            {
                "id": 18,
                "min": 1,
                "max": 1,
                "definition": {
                    "id": 8,
                    "contexts": [],
                }
            },
            {
                "id": 18,
                "min": 1,
                "max": 1,
                "definition": {
                    "id": 8,
                    "contexts": []
                }
            }
        ]
    }
}

My aim is to change the "min" and "max" values without making Neo4J creating a new RelationshipEntity id.

Here is my ContextRelation.class :

@RelationshipEntity(type = RelationType.Names.CONTEXT)
public class ContextRelation extends BaseRelation {
    @StartNode
    private BaseDefinition from;
    @EndNode
    private BaseDefinition to;
    private Long min;
    private Long max;

    public ContextRelation() {}
    public ContextRelation(BaseDefinition from, BaseDefinition to, Long min, Long max) {
        this.from = from;
        this.to = to;
        this.min = min;
        this.max = max;
    }

    @JsonProperty("definition")
    public BaseDefinition getTo() {
        return this.to;
    }

    public Long getMin() {
        return this.min;
    }
    public void setMin(Long min) {
        this.min = min;
    }

    public Long getMax() {
        return this.max;
    }
    public void setMax(Long max) {
        this.max = max;
    }
}

BaseDefinition.class:

@NodeEntity(label = NodeType.Names.DEFINITION)
public abstract class BaseDefinition extends BaseGraphObject {
    @JsonFilter("DEPTH_ENTITY")
    @Relationship(value = RelationType.Names.CONTEXT)
    protected Set<ContextRelation> contexts;

    public BaseDefinition() {
        super();
        this.contexts = new ConcurrentSet<>();
    }

Here is the place where I edit Set<ContextRelation> in the Controller:

if(json.get("contexts") instanceof List) {
    Map<Long, Map<String, Object>> jsonContexts = new HashMap<>();

    ((List<Map<String, Object>>) json.get("contexts")).stream()
            .filter(ContextRelation::validate)
            .filter(c -> !jsonContexts.keySet().contains(Long.valueOf(c.get("definition").toString())))
            .forEach(c -> jsonContexts.put(Long.valueOf(c.get("definition").toString()), c));

    Map<Long, ContextRelation> definitionContexts = definition.getContexts().stream()
        .collect(Collectors.toMap(c -> c.getTo().getId(), c -> c));

    for(ContextRelation currentContext : definitionContexts.values()) {
        if(!jsonContexts.keySet().contains(currentContext.getTo().getId())) {
            definition.getContexts().remove(currentContext);
        } else {
            Map<String, Object> currentJsonContext = jsonContexts.get(currentContext.getTo().getId());

            definition.getContexts().remove(currentContext);

            currentContext.setMax(currentJsonContext.get("max") == null ? null : Long.valueOf(currentJsonContext.get("max").toString()));
            currentContext.setMin(currentJsonContext.get("min") == null ? null : Long.valueOf(currentJsonContext.get("min").toString()));

            definition.getContexts().add(currentContext);

            definitionContexts.put(currentContext.getTo().getId(), currentContext);
        }
    }

    for(Map<String, Object> currentContext : jsonContexts.values()) {
        if(ContextRelation.validate(currentContext) && !definitionContexts.keySet().contains(Long.parseLong(currentContext.get("definition").toString()))) {
            BaseDefinition contextDefinition = repository.find(Long.valueOf(currentContext.get("definition").toString()));

            if(contextDefinition == null)
                errors.put("contexts", "One context definition's id is incorrect");
            else
                definition.getContexts().add(
                    new ContextRelation(
                        definition,
                        contextDefinition,
                        currentContext.get("min") == null ? null : Long.parseLong(currentContext.get("min").toString()),
                        currentContext.get("max") == null ? null : Long.parseLong(currentContext.get("max").toString())
                    )
                );
        }
    }
}

definition = repository.createOrUpdate(definition);

ApiUtils.respond(context, "Définition modifée avec succès", Response.OK, definition);

Do you guys have any idea about this problem ?

Thank you

4 REPLIES 4

@gerrit.meier Sorry for the ping, but I'm struggling so hard on this problem and I know you're really confident with Neo4J, do you have an idea on this ?

Okay, I think I've found a solution for this: I think that the fact the ContextRelation is being edited and is part of the BaseDefinition::contexts Set, the OGM thinks it has to update one relation and to create a new one.

Actually in the DB there's only one but, these two instructions let me think that it is unable to share the relation's ids between the instruction. So I tried to force every instructions of the Edit Controller to use the same Neo4J Session, which worked fine !

Even if that solved the problem, I'm not sure if my understanding of the bug is good or not and, I would be glad to have some feedback on it

Welcome to the Neo4j community and sorry for the late reply.
Besides the code your assumption is right: You should always operate within the same session for one unit of work (load, edit, save).
Unfortunately I cannot see where the definition comes from in your last snippet.

Actually in the DB there's only one but, these two instructions let me think that it is unable to share the relation's ids between the instruction

Do you mean before or after the edit?

Sorry for the late reply, the notification email was flagged as spam
I mean after the edit, that's what was strange, it returned me two relations but when doing a MATCH (n) RETURN n; through the Neo4J Browser, the graph was only containing one relationship

Nodes 2022
Nodes
NODES 2022, Neo4j Online Education Summit

On November 16 and 17 for 24 hours across all timezones, you’ll learn about best practices for beginners and experts alike.