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 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
.