Cannot implement CRUD operations through Neo4j in Spring Boot

555 Views Asked by At

I have a problem about implementing CRUD operations through the Neo4j query in Spring Boot.

My issue is located at both CityRepository, RouteRepository, ShortestPathRepository and Route entity.

1 ) When I called listAll and getById method of CityRepository, I get empty city name with listing its route after adding its route shown below.

[
    {
        "id": "077d1b16-9a4b-4fb0-947b-52031774d949",
        "name": "London",
        "routes": []
    },
    {
        "id": "077d1b16-9a4b-4fb0-947b-52031774d949",
        "name": null,
        "routes": [
            {
                "id": "6db5dd3f-085a-4d50-b025-4f0bee847fcf",
                "from": "London",
                "destination": "Berlin",
                "departureTime": "9:00",
                "arriveTime": "11:30",
                "duration": 2.5
            }
        ]
    }
]

2 ) (Edited) After adding any route of City and calling listAll of City, I got this result shown in the screenshot.

enter image description here enter image description here

Here is my project link : Project

Here is the postman request collection : Link

Here are screenshots related with my issues : Link

How can I fix my issue?

Here are my entities as City and Route shown below.

City

public class City {

    @Id
    @Property
    private UUID id;

    @Property
    private String name;

    @Relationship(type = "ROUTES", direction = Relationship.Direction.OUTGOING)
    private Set<Route> routes = new HashSet<>();

    public City(String name) {
        this.name = name;
    }
}

Route

public class Route {

    @Id
    @Property
    private UUID id;

    @Property
    private String from;

    @Property
    private String destination;

    @Property
    private String departureTime;

    @Property
    private String arriveTime;

    @Property
    private Double duration;
}

Here is my CityRepository shown below.

public interface CityRepository extends Neo4jRepository<City,UUID> {

    @Query("MATCH (city:City) OPTIONAL MATCH (city)-[r:ROUTES]->(route:Route) RETURN city, collect(r), collect(route)")
    List<City> listAll();

    @Query("MATCH (city:City {id: $cityId}) OPTIONAL MATCH (city)-[r:ROUTES]->(route:Route) RETURN city, collect(r), collect(route)")
    City getById(UUID cityId);

    @Query("MATCH (city:City {name: $cityName}) RETURN city")
    City getByCityName(String cityName);

    @Query("CREATE (city:City {id: randomUUID(), name: $cityName}) RETURN city")
    City saveCity(String cityName);

    @Query("MATCH (city:City {id: $cityId}) SET city.name = $cityName RETURN city")
    City updateCity(UUID cityId, String cityName);

    @Query("MATCH (city:City {id: $cityId}) DELETE city")
    void deleteCity(UUID cityId);
}

Here is my RouteRepository shown below.

public interface RouteRepository extends Neo4jRepository<Route,UUID> {

    @Query("MATCH (city:City {id: $cityId})-[:ROUTES]->(route:Route) RETURN route")
    List<Route> listAllByCityId(UUID cityId);

    @Query("MATCH (route:Route {id: $routeId}) RETURN route")
    Route getById(UUID routeId);

    @Query("CREATE (city:City {id: $cityId})-[:ROUTES]->(route:Route {id: randomUUID(), from: $from, destination: $destination, departureTime: $departureTime," +
            "arriveTime: $arriveTime, duration: $duration}) " +
            "RETURN route")
    Route saveRoute(UUID cityId, String from, String destination, String departureTime,
                    String arriveTime, double duration);

    @Query("MATCH (city:City {id: $cityId})-[:ROUTES]->(route:Route {id: $routeId}) " +
            "SET route.from = $from, route.destination = $destination,route.departureTime = $departureTime," +
            "route.arriveTime = $arriveTime, route.duration = $duration RETURN route")
    Route updateRoute(UUID cityId, UUID routeId, String from, String destination,String departureTime,
                      String arriveTime,double duration);

    @Query("MATCH (city:City {id: $cityId})-[r:ROUTES]->(route:Route {id: $routeId}) DELETE r, route")
    void deleteRoute(UUID cityId, UUID routeId);
}
2

There are 2 best solutions below

0
On BEST ANSWER

Third update: There is a problem with the custom save query. With the CREATE.... in place, Neo4j will always create new entities. The safest way to create a new relationship with at least one existing node is to match on the existing node and then use it by naming reference in the MERGE statement. I updated my project but here is basically the solution:

@Query("MATCH (city:City {id: $cityId}) " +
       "MERGE (city)-[:ROUTES]->(route:Route {id: randomUUID(), from: $from, destination: $destination, departureTime: $departureTime," +
            "arriveTime: $arriveTime, duration: $duration}) " +
            "RETURN route")
Route saveRoute(UUID cityId, String from, String destination, String departureTime,
                    String arriveTime, double duration);

Second update: Alright, I updated the project to include web tests now starting on the controller level. Please have a look and adjust it to reproduce the errors that I am missing out right now.

I cannot reproduce 1 and 4, please have a look at com.springshortpath.app.ApplicationWebTests#listCitiesWithRoutes / com.springshortpath.app.ApplicationWebTests#listRoutesByCity.

For 2 and 3: the query pattern demands to have (at least) one route defined. In the case of also wanting to get cities without routes, you would have to go with optional match.

For 5: Spring Data Neo4j's repositories can handle entity definitions only as a return type. If you want to use the Java driver's type, please use SDN's Neo4jClient for interaction. SDN documentation / Neo4jClient

Update to the edited question:

For the deletion of the route: The error states that the relationship has to get removed before. The correct query should be MATCH (city:City {id: $cityId})-[r:ROUTES]->(route:Route {id: $routeId}) DELETE r, route

For the unable to find routes by city UUID: The direction of the relationship is wrong, the correct one is: MATCH (city:City {id: $cityId})-[:ROUTES]->(route:Route) RETURN route

I could not reproduce the other problems but created test cases for what I understand in a fork of your repository: https://github.com/meistermeier/SpringBootNeo4jShortestPath

Old answers: In general, I see a misconception of id property vs. the id of a node.

1 ) When I called saveCity method, the value with its id value 0 was added. I have no idea why id is assigned to 0.

You defined your id to be the internal Neo4j one.

@Id
@GeneratedValue
private Long id;

As a result SDN will assign this value to the id field on save. The internal id of a node will be compared/retrieved via id(node) in the Cypher query.

3 ) When I called getCityById method, I couldn't see any result

As a consequence the query MATCH (city:City {id: $cityId}) RETURN city will not look for the right id field but for a property id.

2 ) When I called listAll method, all values were listed (There is no Route value as I cannot add Route)

If you are still referring to the CityRepository here, you cannot see any Route because you don't return them. Altering the query to MATCH (city:City)-[r:ROUTES]->(route:Route) RETURN city, collect(r), collect(route) (or adding an optional clause to get the routes instead of requiring the pattern), will also add the Routes to the corresponding Cities.

4 ) When I called updateCity method, I couldn't see any result.

This should work as expected but I assume that you are referring to something else by saying couldn't see any result. It should be the same result as the method City getByCityName(String cityName) including the id property change.

For the rest of the questions, I bet it's the node.id property vs. id(node) problem again. So basically every place you have (n:City|Route...{id: $id}) should become (n:City|Route...) WHERE id(n) = $id.

0
On

Please find below response from my end.

1 ) When I called saveCity method, the value with its id value 0 was added. I have no idea why id is assigned to 0.

My Reply: when you try to add into db id value starts from zero that is default zero. when you add second record then id value will be 1...similary when you add third record id value will be 2 and so on.

2 ) When I called listAll method, all values were listed (There is no Route value as I cannot add Route)

My Reply: This is default behavior.

  1. When I called getCityById method, I couldn't see any result My Reply: you can with getById or getId

4)4 ) When I called updateCity method, I couldn't see any result.

5 ) When I called deleteCity method, I saw "City is deleted" but when I called listAll method, I see that value. As I got it, delete method cannot be worked.

6 ) When I called saveRoute, it was added but there is an id issue. I cannot see its value when I called listAll of City method.

7 ) When I called getRouteById method, I couldn't see any result

8 ) When I called listAll method, I couldn't see any result.

9 ) When I called updateRoute method, I couldn't see any result.

10 ) When I called deleteRoute method, I saw "Route is deleted" but when I called listAll method, I couldn't see any result. I have no idea if it is deleted.

My Reply : for 4 ,5,6,7,8,9 i think you are not commiting or saving with respect to programming. after commit operation only results will be saved or updated.