@RelationshipEntity: Rich relationships - Repository

Hi all,

We need to createOrUpdate rich relationships over existing nodes. While using the repository for RelationshipEntitiy it executes CREATE statements if it has any property and creates duplicate relation if Id is null instead of using MERGE to avoid any duplicates. We need to avoid SELECT stmts when Neo4j offers MERGE.

This happens because of NewRelationshipStatementBuilder :

 if (hasProperties && !hasPrimaryId) {
    queryBuilder.append("CREATE ");
} else {
    queryBuilder.append("MERGE ");
}

Current implementation builds a safe MERGE query only if a RelationshipEntity has not any property defined rather than @Id, @StartNode and @EndNode.
Why properties of relationship should be consider to execute CREATE or MERGE stmt and not only the Id of the relationship? It would be nice to control the behavior of this action by overriding a method like with isNew() for persisting entities with hibernate.

In addition if a node has @Id defined as type Long then while saving a relationship it executes MATCH and SET in order to update the property values of the node but if @Id is defined as any other type then a MERGE is executed.


UNWIND {rows} as row MERGE (n:`Movie`{id: row.props.id}) SET n=row.props RETURN row.nodeRef as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeRef=-2, props={id=42L}}]}

UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId WITH row,startNode MATCH (endNode) WHERE ID(endNode) = row.endNodeId CREATE (startNode)-[rel:`PLAYED_IN`]->(endNode) SET rel += row.props RETURN row.relRef as ref, ID(rel) as id, {type} as type with params {type=rel, rows=[{startNodeId=42, relRef=-1, endNodeId=41, props={title=title}}]}

UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`Actor` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=42, props={}}]}

UNWIND {rows} as row MERGE (n:`Movie`{id: row.props.id}) SET n=row.props RETURN row.nodeRef as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeRef=-5, props={id=23L}}]}

UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId WITH row,startNode MATCH (endNode) WHERE ID(endNode) = row.endNodeId MERGE (startNode)-[rel:`TEST`]->(endNode) RETURN row.relRef as ref, ID(rel) as id, {type} as type with params {type=rel, rows=[{startNodeId=23, relRef=-4, endNodeId=20, props={}}]}

UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`Actor` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=23, props={}}]}

Node entities:

@Data
@Builder
@NodeEntity
public class Actor {
  @Id Long id;

  @Relationship(type = "PLAYED_IN")
  private RoleRelation playedIn;
}
@Data
@Builder
@NodeEntity
public class Movie {
  @Id private String id;

  @Relationship(type = "PLAYED_IN", direction = INCOMING)
  private RoleRelation playedIn;
}

Relationship Entities:

@Data
@Builder
@RelationshipEntity(type = "PLAYED_IN")
public class RoleRelation {
  @Id @GeneratedValue private Long relationshipId;
  @Property private String title;
  @StartNode private Actor actor;
  @EndNode private Movie movie;
}
@Data
@Builder
@RelationshipEntity(type = "TEST")
public class TestRelation {
  @Id @GeneratedValue private Long relationshipId;
  @StartNode private Actor actor;
  @EndNode private Movie movie;
}

documentation reference:

Additionally, the OGM will not persist a relationship entity that doesn’t have any properties defined. If you don’t want to include properties in your relationship entity then you should use a plain @Relationship instead. Multiple relationship entities which have the same property values and relate the same nodes are indistinguishable from each other and are represented as a single relationship by the OGM.

Version of dependencies:
org.springframework.data:spring-data-neo4j:5.0.12.RELEASE
org.neo4j:neo4j-ogm-core:3.1.3
org.neo4j:neo4j-ogm-api:3.1.3
org.neo4j:neo4j-ogm-bolt-driver:3.1.3

the issue for relation id is also discussed here : Rehydrating @RelationshipEntity without the rel 'id'

for hibernate there is a way to create a generator that allows you to mix a manually-assigned identifier with an automatic assigning strategy: https://vladmihalcea.com/how-to-combine-the-hibernate-assigned-generator-with-a-sequence-or-an-identity-column/