Certain node connections are not persisting in Neo4j via SpringData

Viewed 66

Im very new to Java and Spring, so im trying to make a simple app that add vehicle measurements to a vehicle in a linked list style:

Desired Structure

Ive had a lot of trouble figuring out whether to use the neo4j.ogm annotations of the springframework.data.neo4j annotations. Currently im settled on the springframework annotations because the ogm ones cause an improper Id execption. In my code snippets i consolidated like members into the same snippet to reduce redundancy.

Here is my build.gradle:

plugins {
    id 'org.springframework.boot' version '2.4.3'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-neo4j'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}

test {
    useJUnitPlatform()
}

Here is my application root:

package com.example.neo4j;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;

@SpringBootApplication
@EnableNeo4jRepositories
public class Test2Application {

    public static void main(String[] args) {
        SpringApplication.run(Test2Application.class, args);
    }

    @Bean
    CommandLineRunner demo(VehicleRepository vehicleRepository, VehicleMeasurementRepository vehicleMeasurementRepository, VehicleMeasurementService vehicleMeasurementService) {
        return args -> {
            vehicleRepository.deleteAll();
            vehicleMeasurementRepository.deleteAll();

            Vehicle vehicle = new Vehicle("vin", "Ford", "Bronco");
            vehicleRepository.save(vehicle);

            VehicleMeasurement first = new VehicleMeasurement(5D, 198D, 100D);
            VehicleMeasurement second = new VehicleMeasurement(10D, 198D, 98D);
            VehicleMeasurement third = new VehicleMeasurement(15D, 198D, 97D);

            vehicleMeasurementService.addNewMeasurementByVin("vin", third);
            vehicleMeasurementService.addNewMeasurementByVin("vin", second);
            vehicleMeasurementService.addNewMeasurementByVin("vin", first);
        };
    }
}

Here are my Entities:

package com.example.neo4j;

import lombok.Data;
import lombok.NoArgsConstructor;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Relationship;


@Data
@Node
@NoArgsConstructor
public class Vehicle {

    @Id @GeneratedValue
    private Long id;

    private String vin, make, model;

    public Vehicle(String vin, String make, String model) {
        this.vin = vin;
        this.make = make;
        this.model = model;
    }


    @Relationship(type = "HAS_HEAD_MEASUREMENT", direction = Relationship.Direction.OUTGOING)
    @JsonIgnoreProperties({"next", "previous"})
    private VehicleMeasurement head;


    @Relationship(type = "HAS_TAIL_MEASUREMENT", direction = Relationship.Direction.OUTGOING)
    @JsonIgnoreProperties({"next", "previous"})
    private VehicleMeasurement tail;
    public VehicleMeasurement getHead() {
        return head;
    }
}

@Node
@Data
@NoArgsConstructor
public class VehicleMeasurement {

    @Id @GeneratedValue
    private Long id;

    private Double mileage, engineTemperature, oilLife;

    public VehicleMeasurement(Double mileage, Double engineTemperature, Double oilLife) {
        this.mileage = mileage;
        this.engineTemperature = engineTemperature;
        this.oilLife = oilLife;
    }

    @Relationship(type = "NEXT", direction = Relationship.Direction.OUTGOING)
    @JsonIgnoreProperties({"head", "tail", "vehicleHeadReference", "vehicleTailReference"})
    private VehicleMeasurement next;
}

Here are my Repositories:

package com.example.neo4j;

import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;

import java.util.List;

public interface VehicleRepository extends Neo4jRepository<Vehicle, Long> {
    Vehicle findVehicleByVin(String vin);
}

public interface VehicleMeasurementRepository extends Neo4jRepository<VehicleMeasurement, Long> {

    @Query("MATCH (v:Vehicle {vin: $vin})-[*]->(vm:VehicleMeasurement)-[*]-(v)" +
            "RETURN DISTINCT vm ORDER BY vm.mileage ASCENDING")
    List<VehicleMeasurement> findVehicleMeasurementsByVehicleVin(String vin);
}

And here is my service:

package com.example.neo4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Iterator;
import java.util.Optional;

@Service
public class VehicleMeasurementService {

    @Autowired
    VehicleMeasurementRepository vehicleMeasurementRepository;

    @Autowired
    VehicleRepository vehicleRepository;

    public void addNewMeasurementByVin(String vin, VehicleMeasurement measurement) {
        Vehicle targetVehicle = vehicleRepository.findVehicleByVin(vin);

        Iterator<VehicleMeasurement> vehicleMeasurements = vehicleMeasurementRepository.findVehicleMeasurementsByVehicleVin(vin).iterator();

        Optional<VehicleMeasurement> left = Optional.empty();
        Optional<VehicleMeasurement> right = Optional.empty();

        while (vehicleMeasurements.hasNext()) {
            var next = vehicleMeasurements.next();

            if (next.getMileage() <= measurement.getMileage()) {
                left = Optional.of(next);
            }

            if (measurement.getMileage() <= next.getMileage() && right.isEmpty()) {
                right = Optional.of(next);
            }
        }

        if (left.isEmpty() && right.isEmpty()){
            targetVehicle.setHead(measurement);
            targetVehicle.setTail(measurement);
        } else if (left.isEmpty()) {
            targetVehicle.setHead(measurement);
            measurement.setNext(right.get());
            vehicleMeasurementRepository.save(right.get());
        } else if (right.isEmpty()) {
            VehicleMeasurement leftValue = left.get();
            leftValue.setNext(measurement);
            vehicleMeasurementRepository.save(leftValue);
            targetVehicle.setTail(measurement);
        }

        vehicleMeasurementRepository.save(measurement);
        vehicleRepository.save(targetVehicle);
    }
}

I run the add method 3 times and debug between them to see these results:

result after first rendition
enter image description here
enter image description here

The last picture is the problem, when I drill into the nodes during debugging I see the references I expect but then it finishes running the connection between node 98 and node 97 does not persist.

Any advice would be greatly appreciated, stylewise, useage, or method anything helps. I anticipate im using the spring data incorrectly. I tried a rendition with two way references and it would blow the stack, even with my ignore properties annotation.

0 Answers
Related