Custom Cypher for Read

So far, you have used Spring Data’s derived find, save, and delete methods against the database. However, there are times when you need something a little different. This is where you can write custom Cypher queries for read operations.

Defining a custom repository method

Currently you have only implemented the Spring Data’s out-of-the-box findAll(), findById(), save(), and deleteById() methods. These methods implement a standard use case for querying and saving data.

When you need something specific, you can define your own methods, and write Cypher statements that query the data and map the results back to a custom method.

For instance, the findAll() method works fine to pull the nodes in this movie graph, but what if you had a graph multiple times larger? Even at the current size, it is difficult to view all that data returned on the console. Instead, you could write a custom method to pull a random, smaller subset of the graph each time.

To modify the findAll() method you will need to:

  1. Add a new Query method to the MovieRepository interface:

    • Open the MovieRepository.java interface in src/main/java/com/example/appspringdata

    • Import the org.springframework.data.neo4j.repository.query.Query class.

      java
      import org.springframework.data.neo4j.repository.query.Query;
    • Add a new findMoviesSubset() method that uses the @Query annotation to run a Cypher statement.

      java
      interface MovieRepository extends Neo4jRepository<Movie, String> {
          @Query("MATCH (m:Movie)<-[r:ACTED_IN]-(p:Person)" +
                  "RETURN m, collect(r), collect(p) LIMIT 20;")
          Iterable<Movie> findMoviesSubset();
      }

      The interface returns an Iterable<Movie>.

  2. Modify the MovieController class to call the new query:

    • Open the `MovieRepository.java class.

    • Update the existing findAllMovie() method to call the new query method.

      java
          @GetMapping()
          Iterable<Movie> findAllMovies() {
              return movieRepo.findMoviesSubset();
          }

Full code for the MovieRepository and MovieController classes is available in the dropdown below.

Click to reveal the completed MovieRepository and MovieController class code
java
MovieRepository.java
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;

interface MovieRepository extends Neo4jRepository<Movie, String> {
    @Query("MATCH (m:Movie)<-[r:ACTED_IN]-(p:Person)" +
            "RETURN m, collect(r), collect(p) LIMIT 20;")
    Iterable<Movie> findMoviesSubset();
}
java
MovieController.java
@RestController
@RequestMapping("/movies")
public class MovieController {
    private final MovieRepository movieRepo;

    public MovieController(MovieRepository movieRepo) {
        this.movieRepo = movieRepo;
    }

    @GetMapping()
    Iterable<Movie> findAllMovies() {
        return movieRepo.findMoviesSubset();
    }

    @GetMapping("/{movieId}")
    Optional<Movie> findMovieById(@PathVariable String movieId) {
        return movieRepo.findById(movieId);
    }

    @PostMapping("/save")
    Movie save(@RequestBody Movie movie) {
        return movieRepo.save(movie);
    }

    @DeleteMapping("/delete")
    void delete(@RequestParam String movieId) {
        movieRepo.deleteById(movieId);
        System.out.println("Deleted movie with movieId: " + movieId);
    }
}

RETURN aggregation

In the RETURN statement for the query above, each Movie node is returned with its collected ACTED_IN relationships and Person nodes. This is because the method expects Movie nodes, and then aggregates the related entities (relationships and nodes) for each unique movie using Cypher’s implicit aggregation for each item listed in the RETURN statement.

Testing the findMoviesSubset() custom method

Once you make the modifications above to your project’s interface (new custom query) and controller (call findMoviesSubset()), test the new method by running the application and calling the /movies endpoint. You should see a response with a subset of movies, each with their ACTED_IN relationships and Person nodes.

shell
curl 'localhost:8080/movies'

To take this a step further, you can define one more custom method that uses a search parameter. This will be covered in the next lesson.

Summary

In this lesson, you learned how to write your own custom methods and queries to retrieve data from the database.

Next, you will complete a challenge that builds upon that skill to create custom queries and methods for writing data to the database.