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.
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);
}
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 theMERGE
statement. I updated my project but here is basically the solution: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 / Neo4jClientUpdate 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. theid
of a node.You defined your id to be the internal Neo4j one.
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.As a consequence the query
MATCH (city:City {id: $cityId}) RETURN city
will not look for the right id field but for a propertyid
.If you are still referring to the
CityRepository
here, you cannot see anyRoute
because you don't return them. Altering the query toMATCH (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 theRoutes
to the correspondingCities
.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
.