How Spring Data Neo4j abstraction handles creation & update (repository.save)

Viewed 167

For the purposes of testing, I created a simple domain in the form of a tree of companies and their orders, so:

  • company delivers orders
  • company has only one company above
  • company can have multiple collectors below

And this is how I represented it:

@Node
public class Company {

    @Id
    @GeneratedValue(UUIDStringGenerator.class)
    private String id;

    private String name;

    @Relationship(type = "COLLECTS_FROM", direction = INCOMING)
    private Company parent;

    @Relationship(type = "COLLECTS_FROM")
    private Set<Company> children;

    @Relationship(type = "DELIVERS")
    private Set<Order> orders;

    //all args constructor with (thid.id = null)
    //getters and setters (omitted for brevity)
}   

@Node
public class Order {

    @Id
    @GeneratedValue(UUIDStringGenerator.class)
    private String id;
    private final String partNumber;
    private final Integer quantityOrdered;
    private final Integer quantityReceived;
    
    //all args constructor with (thid.id = null)
    //getters and setters (omitted for brevity)

Of course, I have two very simple repositories:

@Repository
public interface CompanyRepository extends Neo4jRepository<Company, String> {
}
@Repository
public interface OrderRepository extends Neo4jRepository<Order, String> {
}

I created few integration tests:

    @Test
    void shouldCreateCompanyWithRelations() {
        Company parent = companyRepository.save(new Company("parent", null, null, null));
        Company firstChild = companyRepository.save(new Company("Child1", null, null, null));
        Company secondChild = companyRepository.save(new Company("Child2", null, null, null));
        Order firstOrder = orderRepository.save(new Order("part1", 10, 10));
        Order secondOrder = orderRepository.save(new Order("part2", 20, 20));
        Company testCompany = companyRepository.save(new Company("TestCompany", parent, Set.of(firstChild, secondChild), Set.of(firstOrder, secondOrder)));

        var result = companyRepository.findById(testCompany.getId());

        assertThat(result)
                .hasValueSatisfying(company -> {
                    assertThat(company.getId()).isNotNull();
                    assertThat(company.getName()).isEqualTo("TestCompany");
                    assertThat(company.getParent()).isNotNull(); //this assertion fails, returns null
                    assertThat(company.getChildren()).hasSize(2); //this assertion fails, returns 0
                    assertThat(company.getOrders()).hasSize(2);
                });
    }
  • Expectation: all relations will be created.
  • Reality: test fails due to missing relation to parent & children (same node), relation to orders exist (different node)
    @Test
    void createWithSingleSave() {
        Company parent = new Company("parent", null, null, null);
        Company firstChild = new Company("Child1", null, null, null);
        Company secondChild = new Company("Child2", null, null, null);
        Order firstOrder = new Order("part1", 10, 10);
        Order secondOrder = new Order("part2", 20, 20);
        Company testCompany = companyRepository.save(new Company("TestCompany", parent, Set.of(firstChild, secondChild), Set.of(firstOrder, secondOrder)));

        var result = companyRepository.findById(testCompany.getId());
  • Expectation: cascade creation of all companies & orders
  • Reality: org.springframework.data.mapping.MappingException: Error mapping Record<{__sn__: node<0>, __sr__: [relationship<0>, relationship<1>, relationship<2>, relationship<3>, relationship<4>], __srn__: [node<0>, node<1>, node<2>, node<3>, node<4>, node<5>]}>

I have the assumption, that maybe it's not the repo.save issue, but repo.findById() issue, that' why I created the third test:

    @Test
    void basicPropertyUpdate() {
        //not sure if this initializatio work, because of the issue mentioned on test 1 & 2
        Company parent = companyRepository.save(new Company("parent", null, null, null));
        Company firstChild = companyRepository.save(new Company("Child1", null, null, null));
        Company secondChild = companyRepository.save(new Company("Child2", null, null, null));
        Order firstOrder = orderRepository.save(new Order("part1", 10, 10));
        Order secondOrder = orderRepository.save(new Order("part2", 20, 20));
        Company testCompany = companyRepository.save(new Company("TestCompany", parent, Set.of(firstChild, secondChild), Set.of(firstOrder, secondOrder)));

        var companyToUpdate = companyRepository.findById(testCompany.getId()).orElseThrow();
        companyToUpdate.setName("NewName");
        var updatedCompany = companyRepository.save(companyToUpdate);
  • Expectation: will save only basic property
  • Concern: if parent & children aren't nulls, and that's the findById issue while performing basic property update also the relation to parent & child will be overridden and will remove them.

I didn't write the fourth test, but it is related to how we should update relations e.g. by adding or removing the child set, relations of relations are safe or still can be overridden because relation nodes of relation nodes can be also nulls?

I have read all the documentation, but it didn't give me a clear answer (I will say more, it caused more misunderstanding). I used Spring Boot 2.5.5 with spring-boot-starter-data-neo4j (Spring data neo4j 6.1.5)

I've given quite a bit of an introduction, but the question(s) seems relatively simple.

  1. Why am I not able to create the above-mentioned structure (with the repository.save)? Or why is findById unable to recreate it?
  2. How to correctly update basic property & relation of the existing nodes (with the repository.save) without fear that something will be accidentally deleted?
0 Answers
Related