Neo4jRepository.save does not persist updated relation


(Moritz Loeser) #1

Until today we relied on the return value Neo4jRepository.save as an confirmation that update was successful but it seems that the returned entity does not reflect the persisted state. In our case updates on relations never got persisted.

This is a example of one of our relations:

@RelationshipEntity(type = "responsible_for_product")
@Data
public class ProductResponsibleUnitRelation {

    @Id
    @GeneratedValue
    private Long id;


    @StartNode
    private LegalEntityNode legalEntity;

    @EndNode
    private ProductHierarchyNode productHierarchy;

    private LocalDate validFrom;

    private LocalDate validTo;

}


@NodeEntity
@Data
public class LegalEntityNode {
    @Id
    private String legalEntityId;

}

@NodeEntity
@Data
public class ProductHierarchyNode {
    @Id
    private String id;

}

I created a integration test that was fine as long as i checked only the entity returned by save (in JPA test i mostly use saveAndFlush) now it is failing:

    @Autowired
    private ProductResponsibleUnitRepository repository;

    @Test
    public void crudTest() {
        //create
        ProductHierarchyNode productHierarchyNode = random(ProductHierarchyNode.class);
        productHierarchyNode.setId("123-456-789");

        LegalEntityNode legalEntityNode = random(LegalEntityNode.class);
        legalEntityNode.setLegalEntityId("1");

        ProductResponsibleUnitRelation productResponsibleUnitRelation = new ProductResponsibleUnitRelation();
        productResponsibleUnitRelation.setProductHierarchy(productHierarchyNode);
        productResponsibleUnitRelation.setLegalEntity(legalEntityNode);
        productResponsibleUnitRelation.setValidFrom(LocalDate.now());
        productResponsibleUnitRelation.setValidTo(LocalDate.now().plusYears(1));

        ProductResponsibleUnitRelation savedPru = repository.save(productResponsibleUnitRelation);

        Long id = savedPru.getId();
        assertNotNull(id);
        //read
        Optional<ProductResponsibleUnitRelation> readPruOptional = repository.findById(id);
        assertTrue(readPruOptional.isPresent());
        assertThat(readPruOptional.get(), is(savedPru));

        //update
        ProductHierarchyNode productHierarchyNode1 = new ProductHierarchyNode();
        String newPhId = "4711";
        productHierarchyNode1.setId(newPhId);

        productResponsibleUnitRelation.setProductHierarchy(productHierarchyNode1);
        repository.save(productResponsibleUnitRelation);

        Optional<ProductResponsibleUnitRelation> readPruOptionalUpdated = repository.findById(id);
        assertTrue(readPruOptionalUpdated.isPresent());
        // THIS IS FAILING - still "123-456-789" the relation is still connected to "123-456-789"
        assertThat(readPruOptionalUpdated.get().getProductHierarchy(), is(productHierarchyNode1));


        //delete
        repository.deleteById(id);

        assertFalse(repository.findById(id).isPresent());

    }

While the new productHierarchyNode1 is persisted correctly it is not connected/ no part of the realation:

image

After correct save "1" should be "responsible for" "4711"

This is not only happening in tests but also in "real" - interacting through service and rest layer with the repository.

So how to get updating relations (especially changing start and end node) working


(Gerrit Meier) #2

One reason for this behaviour could be that the read and update parts are not happening in the same transaction. So basically these both operations should happen in some kind of service layer that has a @Transactional annotated method.
It looks like the it seems that the returned entity does not reflect the persisted state does reflect the wrong persisted state, or?
If this does not help, please set the log level to debug and post it to see what is going on. Adding some kind of indicators where the update happens would be helpful

Just as a note: Beside the technical possibility to work relationship centric with Spring Data Neo4j / Neo4j-OGM, I would suggest working with the nodes (e.g. a LegalEntityRepository depending on your domain model). A relationship query is always more complex and needs at least also the start and end nodes in the outgoing Cypher statement, for example. This is unrelated to the problem you are facing.


(Moritz Loeser) #3

The problem is not that there is nothing persisted but what is persisted is inconsistent:
The first save creates an Relation entity this works perfectly fine, right after save i can see the result in Neo4j (halt on breakpoint, but test is also checking this) .
The next call of save is an update but this time for some unknown reason only the new node is persisted but the relation itself is not updated while the return value reflects a correctly updated relation.
Also wrapping it in transactions does not help (and should not necessary) - this behavior we see also if we are using all layers and try to update a Relation through REST.

The Neo4JRepository.save() seems never work correctly in case on an update. We see the same behavior with another but similar Relation.

At the moment we are bound to the current data model (we'll probably change in future). So is there a way to get update working? (at the moment we do delete and save again as a bad workaround)


(Gerrit Meier) #4

Ok, got it. I was wondering why and how the Java object model got overwritten with the wrong values. This was a misinterpretation of the test case, sorry.
If the transactional boundaries do not help, please try to provide an outgoing relationship in LegalEntityNode that points to ProductResponsibleUnitRelation with the same responsible_for_product type.
If nothing of this helps please create an issue https://github.com/neo4j/neo4j-ogm/issues to track.


(Moritz Loeser) #5

Meanwhile i tried to add a field for the relation on both sides (tried also only on one side) as documentation suggests but it didn't helped:

@NodeEntity
@Data
public class LegalEntityNode {
    @Id
    private String legalEntityId;

    @EqualsAndHashCode.Exclude
    @Relationship(type = "responsible_for_product")
    private ProductResponsibleUnitRelation responsibleFor;

}

Since there is no other code from me but the entities there is nothing more i can do?!
Now i will create my own update method in Repository that deletes and creates a new relation.

I hope i find time to create a minimal example to reproduce the problem and file a bug.


(Gerrit Meier) #6

I found a partial(?) solution for your problem:
The underlying mechanism creates a second ProductResponsibleUnitRelation relationship when the second save occurs. Neo4j-OGM does not consider this as an existing relationship because the start or end nodes has changed. You can see the state when you skip the deletion at the end of the test.
image
This also means that the id you are trying to get the new information from is the old one. So this is the reason why the test is failing.

Long newId = repository.save(productResponsibleUnitRelation).getId();
Optional<ProductResponsibleUnitRelation> readPruOptionalUpdated = repository.findById(newId);
assertThat(pru.get().getProductHierarchy(), is(newProductNode));

As I said in my previous answer working directly with relationship entities is a little bit more challenging :smiley:


(Moritz Loeser) #7

Thanks for your time!

That means that an relationship's identity is bound to start and end node?!
Then my current workaround is best we can get, right?

In my case the behavior was different. The save on the updated entity returned a correct object (the old id with new nodes). Due to this the problem was not detected for some weeks - i tested against the object returned from save.
At the moment i tested against an object retrieved by the old id the test failed. And in my case the relation between 1 and 4711 was never created.

At the moment i creating a minimal example to play around with it, probably i will file a bug and you could decide what to do with it.

IMHO the current behavior is not correct regarding the expectation about the save(entity) method.


(Moritz Loeser) #8

I created: https://github.com/neo4j/neo4j-ogm/issues/607

including an minimal example with test published in github:

On this way i also found other issues i will probably also try to post.

Hope this helps