Duplicate Relationships being created using SDN / ReactiveNeo4j

Hello. I am implementing a basic node - [relationship] - node, using a NameEntity class, which has a Relationship(type="LINK", direction = INCOMING) annotation.

The Link class has a TargetNode NameEntity.

I have a unit test which creates three nodes, with two relationships. The first time I run the unit test, the nodes and relationships are created:

image

The second time I run the unit test, duplicate relationships are being created:

image

I'm pretty new to neo4j (i'm using the community version 4.4.4).

I'm not expecting the duplicate second relationship to be created (with the same Link type). I appreciate I am not using a Version property.

Is it the default behaviour for neo4j to create a second relationship (with the same attributes). Is there a way for this second (duplicate) relationship to not be created?

I have attached copied NameEntity, and Link pojo, the unit test, and the Cypher which is run (first and second) time I run the unit test.

Any help would be appreciated.

Thanks

Miles.


NameRepository interface:

public interface NameRepository extends ReactiveNeo4jRepository<NameEntity, String>

NameEntity:

@Node("Name")
public class NameEntity {

@Id
private String name;

private String type;

@Relationship(type = "LINK", direction = INCOMING)
private Set<Link> nameLinks;


public NameEntity(final String name, final String type) {
    this.name = name;
    this.type = type;
}

public NameEntity() {}


Link

@RelationshipProperties
@Getter
public class Link {

@RelationshipId
private Long id;

private String type;


@TargetNode
private NameEntity nameEntity;

public Link(NameEntity nameEntity, String type) {
    this.nameEntity = nameEntity;
    this.type = type;
}

public Link() {}


Spring Boot first time run unit test:

2022-04-13 18:31:33.243 DEBUG 25444 --- [o-auto-1-exec-1] .d.n.c.t.ReactiveNeo4jTransactionManager : Creating new transaction with name [org.springframework.data.neo4j.repository.support.SimpleReactiveNeo4jRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2022-04-13 18:31:33.464  WARN 25444 --- [o4jDriverIO-2-2] o.s.d.n.c.m.DefaultNeo4jIsNewStrategy    : Instances of class com.chocksaway.neo4j.entity.NameEntity with an assigned id will always be treated as new without version property!
2022-04-13 18:31:33.529 DEBUG 25444 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher    : Executing:
MERGE (nameEntity:`Name` {name: $__id__}) SET nameEntity += $__properties__ RETURN nameEntity
2022-04-13 18:31:33.639 DEBUG 25444 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher    : Executing:
MERGE (nameEntity:`Name` {name: $__id__}) SET nameEntity += $__properties__ RETURN nameEntity
2022-04-13 18:31:33.655 DEBUG 25444 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher    : Executing:
MATCH (startNode:`Name`) WHERE startNode.name = $fromId MATCH (endNode) WHERE id(endNode) = $toId CREATE (startNode)<-[relProps:`LINK`]-(endNode) SET relProps += $__properties__ RETURN id(relProps)
2022-04-13 18:31:33.663 DEBUG 25444 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher    : Executing:
MERGE (nameEntity:`Name` {name: $__id__}) SET nameEntity += $__properties__ RETURN nameEntity
2022-04-13 18:31:33.669 DEBUG 25444 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher    : Executing:
MATCH (startNode:`Name`) WHERE startNode.name = $fromId MATCH (endNode) WHERE id(endNode) = $toId CREATE (startNode)<-[relProps:`LINK`]-(endNode) SET relProps += $__properties__ RETURN id(relProps)
2022-04-13 18:31:33.682 DEBUG 25444 --- [o4jDriverIO-2-2] .d.n.c.t.ReactiveNeo4jTransactionManager : Initiating transaction commit
2022-04-13 18:31:33.762  INFO 25444 --- [ionShutdownHook] o.neo4j.driver.internal.InternalDriver   : Closing driver instance 2107393518
2022-04-13 18:31:33.764  INFO 25444 --- [ionShutdownHook] o.n.d.i.async.pool.ConnectionPoolImpl    : Closing connection pool towards localhost:7687

Spring Boot second time run unit test:

2022-04-13 18:33:44.751 DEBUG 16572 --- [o-auto-1-exec-1] .d.n.c.t.ReactiveNeo4jTransactionManager : Creating new transaction with name [org.springframework.data.neo4j.repository.support.SimpleReactiveNeo4jRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2022-04-13 18:33:44.981  WARN 16572 --- [o4jDriverIO-2-2] o.s.d.n.c.m.DefaultNeo4jIsNewStrategy    : Instances of class com.chocksaway.neo4j.entity.NameEntity with an assigned id will always be treated as new without version property!
2022-04-13 18:33:45.044 DEBUG 16572 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher    : Executing:
MERGE (nameEntity:`Name` {name: $__id__}) SET nameEntity += $__properties__ RETURN nameEntity
2022-04-13 18:33:45.143 DEBUG 16572 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher    : Executing:
MERGE (nameEntity:`Name` {name: $__id__}) SET nameEntity += $__properties__ RETURN nameEntity
2022-04-13 18:33:45.160 DEBUG 16572 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher    : Executing:
MATCH (startNode:`Name`) WHERE startNode.name = $fromId MATCH (endNode) WHERE id(endNode) = $toId CREATE (startNode)<-[relProps:`LINK`]-(endNode) SET relProps += $__properties__ RETURN id(relProps)
2022-04-13 18:33:45.168 DEBUG 16572 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher    : Executing:
MERGE (nameEntity:`Name` {name: $__id__}) SET nameEntity += $__properties__ RETURN nameEntity
2022-04-13 18:33:45.185 DEBUG 16572 --- [o4jDriverIO-2-2] org.springframework.data.neo4j.cypher    : Executing:
MATCH (startNode:`Name`) WHERE startNode.name = $fromId MATCH (endNode) WHERE id(endNode) = $toId CREATE (startNode)<-[relProps:`LINK`]-(endNode) SET relProps += $__properties__ RETURN id(relProps)
2022-04-13 18:33:45.211 DEBUG 16572 --- [o4jDriverIO-2-2] .d.n.c.t.ReactiveNeo4jTransactionManager : Initiating transaction commit
2022-04-13 18:33:45.290  INFO 16572 --- [ionShutdownHook] o.neo4j.driver.internal.InternalDriver   : Closing driver instance 1728266914
2022-04-13 18:33:45.293  INFO 16572 --- [ionShutdownHook] o.n.d.i.async.pool.ConnectionPoolImpl    : Closing connection pool towards localhost:7687

Unit test

@Test
    public void testAddName() throws URISyntaxException {
        RestTemplate restTemplate = new RestTemplate();
        final String baseUrl = "http://localhost:"+randomServerPort+"/name";

        final Link nameEntity1 = new Link(new NameEntity("name001", "Person"), "Link");
        final Link nameEntity2 = new Link(new NameEntity("name002", "Person"), "Link");

        final NameEntity bob = new NameEntity("Bob", "Person");

        bob.setNameLinks(Set.of(nameEntity1, nameEntity2));

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        final ResponseEntity<String> response =  restTemplate.postForEntity(baseUrl, bob, String.class);

        Assertions.assertEquals(200, response.getStatusCodeValue());

        Assertions.assertTrue(response.hasBody());
        Assertions.assertTrue(response.getBody().contains("Bob"));
    }

Have you implemented hashcode and equals method in Link class.Equals method has to be more detailed (verbose) on source node, target node and direction of link.
Please consider this aspect and review your class design.Data structure Set even though does not allow duplicates but be precise on hashcode and equals implementation so that you do not see what you don't expect to see.

Many thanks
Mr Sameer Sudhir G

Hi Sameer,

Thank you for your answer. Is there a concrete example for reference?

Thanks

Miles.

You already have the important bit in your code:

@RelationshipId
private Long id;

This has to match the previous existing one. Otherwise there is no chance for SDN to recognize it as something already existing.
Your use-case could also be that you want to create another Link with the very same properties, which is a total valid operation.
Maybe you would need some custom merge logic in your "controller/service" that e.g. loads the right entities (NameEntity) first, if they exist and decides if the "new" relationships are really new or you would just use the existing ones.