I have a problem about calculating the shortest path through Neo4j in my Spring Boot example.
After adding some cities with its route, I want to calculate their shortest path in terms of their connection and their duration. However, these two methods which defined in ShortestPathController cannot work.
1 ) getShortestPath throws this kind of error message
Neo.ClientNotification.Statement.UnboundedVariableLengthPatternWarning: The provided pattern is unbounded, consider adding an upper limit to the number of node hops.
MATCH p=shortestPath((a:City {name:$from})-[*]->(b:City {name:$to})) RETURN p
2 ) getShortestPathInTime throws this kind of error message shown below.
org.neo4j.driver.exceptions.ClientException: There is no procedure with the name `apoc.algo.dijkstra` registered for this database instance. Please ensure you've spelled the procedure name correctly and that the procedure is properly deployed.
How can I fix it?
Here is my GitHub repository : Project Link
Here is my Controller class
public class ShortestPathController {
private final ShortestPathService shortestPathService;
public ShortestPathController(ShortestPathService shortestPathService) {
this.shortestPathService = shortestPathService;
}
/* Sample Output (Not working)
{
"arrivalCity": "A",
"departureCity": "B",
"totalConnections": 1
}
*/
@GetMapping("/shortest-path")
public Mono<PathShortestConnectionResponse> getShortestPath(@RequestBody PathRequest pathRequest) {
return shortestPathService.getShortestPath(pathRequest.getFrom(), pathRequest.getDestination())
.map(PathShortestConnectionResponse::new)
.switchIfEmpty(Mono.error(new IllegalArgumentException("Error")));
}
/* Sample Output (Not working)
{
"arrivalCity": "A",
"departureCity": "B",
"totalHours": 5
}
*/
@GetMapping("/shortest-path-in-time")
public Mono<PathShortestTimeResponse> getShortestPathInTime(@RequestBody PathRequest pathRequest) {
return shortestPathService.getShortestPathInTime(pathRequest.getFrom(), pathRequest.getDestination())
.map(PathShortestTimeResponse::new)
.switchIfEmpty(Mono.error(new IllegalArgumentException("Error")));
}
@ResponseStatus(
value = HttpStatus.NOT_FOUND,
reason = "Illegal arguments")
@ExceptionHandler(IllegalArgumentException.class)
public void illegalArgumentHandler() {
}
}
Here is my Service class
public class ShortestPathServiceImpl implements ShortestPathService {
private final ShortestPathRepository shortestPathRepository;
@Override
public Mono<PathShortestConnectionResponse> getShortestPath(String from, String to) {
final Flux<PathValue> rows = shortestPathRepository.shortestPath(from, to);
return rows
.map(it -> this.convert(it.asPath()))
.take(1)
.next()
.switchIfEmpty(Mono.empty());
}
@Override
public Mono<PathShortestTimeResponse> getShortestPathInTime(String from, String to) {
final Flux<PathValue> rows = shortestPathRepository.shortestPathInTime(from, to);
return rows
.map(it -> this.convertTimePath(it.asPath()))
.take(1)
.next()
.switchIfEmpty(Mono.empty());
}
private PathShortestConnectionResponse convert(org.neo4j.driver.types.Path connection) {
String departureCity = connection.start().get("name").asString();
String arriveCity = connection.end().get("name").asString();
int length = connection.length();
return new PathShortestConnectionResponse(departureCity, arriveCity, length);
}
private PathShortestTimeResponse convertTimePath(org.neo4j.driver.types.Path connection) {
String departureCity = connection.start().get("name").asString();
String arriveCity = connection.end().get("name").asString();
Stream<Relationship> targetStream = StreamSupport.stream(connection.relationships().spliterator(), false);
int totalInTime = targetStream.mapToInt(it -> it.get("duration").asInt()).sum();
return new PathShortestTimeResponse(departureCity, arriveCity, totalInTime);
}
}
Here is my Repository class
public interface ShortestPathRepository extends ReactiveNeo4jRepository<City, UUID> {
@Query("MATCH p=shortestPath((a:City {name:$from})-[*]->(b:City {name:$to})) RETURN p")
Flux<PathValue> shortestPath(@Param("from") String from, @Param("to") String to);
@Query("MATCH (a:City {name: $from})\n"
"MATCH (b:City {name: $to})\n"
"CALL apoc.algo.dijkstra(a, b, 'ROUTES', 'duration')\n"
"YIELD path, weight\n"
"RETURN path\n"
"ORDER BY weight ASC LIMIT 1")
Flux<PathValue> shortestPathInTime(@Param("from") String from, @Param("to") String to);
}
Here is my docker-compose.yml shown below.
version: '3'
services:
neo4j-db:
image: neo4j:4.1
container_name: app-neo4j-db
ports:
- 7474:7474
- 7687:7687
volumes:
- $HOME/neo4j/data:/data
- $HOME/neo4j/logs:/logs
- $HOME/neo4j/import:/import
- $HOME/neo4j/plugins:/plugins
environment:
NEO4J_AUTH: neo4j/123456
NEO4J_dbms_security_procedures_unrestricted: apoc.\\\*,gds.\\\*
dbms_connector_bolt_listen__address: neo4j-db:7687
dbms_connector_bolt_advertised__address: neo4j-db:7687
healthcheck:
test: cypher-shell --username neo4j --password 123456 'MATCH (n) RETURN COUNT(n);' # Checks if neo4j server is up and running
interval: 10s
timeout: 10s
retries: 5
app:
image: 'springbootneo4jshortestpath:latest'
build:
context: .
dockerfile: Dockerfile
container_name: SpringBootNeo4jShortestPath
depends_on:
neo4j-db:
condition: service_healthy # Wait for neo4j to be ready
links:
- neo4j-db
environment:
NEO4J_URI: bolt://neo4j-db:7687
NEO4J_PASSWORD: 123456
volumes:
app-neo4j-db:
CodePudding user response:
You need to ensure that APOC library is installed in your Neo4j server. Please follow this installation procedure.
https://neo4j.com/labs/apoc/4.0/installation/#neo4j-server
Then update your conf file to whitelist that function.
CodePudding user response:
Part 1: This is just a warning. In most cases you would not be happy with unbound relationships because the paths can quickly grow in numbers and the server has to keep all possible combination in memory before you will get it.
Part 2: What @jose_bacoy says. Behind the link are informations about how to add APOC in Desktop, Server or Docker deployment. Test your query (or even a reduced version of it) in the shell/browser to ensure it is activated correctly.
Part 2 (again but different topic): You cannot return PathValue
in a repository. If you want to use the Java driver's type, please use SDN's Neo4jClient
for interaction. SDN documentation / Neo4jClient