Challenge: Create Custom Cypher for Write

You have learned how to create, read, and delete nodes in the Neo4j database (the C, R, and D in the popular CRUD acronym). The only part of the puzzle left is to update (U) nodes in the database.

Your challenge is to create a custom method in the repository interface using the @Query annotation with a provided Cypher statement that writes an update to the database. Once you have created the method and query, you will need to test the application and verify the results.

Define a custom repository method

You will update a property in the database to increment the imdbVotes field. While you could do this programmatically through the domain class, it would require either passing all of the property values in the Movie class (recall there are several) or creating a projection (covered in upcoming lessons). A simpler approach, though, is to use a custom Cypher query to update a specific individual property.

To start, open your project’s MovieRepository interface in the src/main/java/com/example/appspringdata folder and add a new method called incrementImdbVotes() that takes a movieId String as a parameter and returns a Movie. Add the @Query annotation to the method and provide the following Cypher statement as the value:

cypher
MATCH (m:Movie {movieId: $movieId})
 SET m.imdbVotes = coalesce(m.imdbVotes+1, 1)
RETURN m;

Your completed MovieRepository interface should match the one provided below.

Click to reveal the completed MovieRepository class code
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();

        @Query("MATCH (m:Movie)<-[r:ACTED_IN]-(p:Person {name: $name})" +
                        "RETURN m, collect(r), collect(p);")
        Iterable<Movie> findMoviesByPerson(String name);

        @Query("MATCH (m:Movie {movieId: $movieId}) " +
                        "SET m.imdbVotes = coalesce(m.imdbVotes+1, 1) " +
                        "RETURN m;")
        Movie incrementImdbVotes(String movieId);
}

Now implement that newly-created method in the MovieController class by creating a new @PutMapping with and endpoint of /movies/updateVotes that takes a movieId string as a request parameter and returns a Movie object. Use the incrementImdbVotes() method to save the value and return the result.

Click to reveal the completed MovieController class code
java
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.PutMapping;

@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);
    }

    @GetMapping("/person")
    Iterable<Movie> findMoviesByPerson(@RequestParam String name) {
        return movieRepo.findMoviesByPerson(name);
    }

    @PutMapping("/updateVotes")
    Movie updateVotes(@RequestParam String movieId) {
        return movieRepo.incrementImdbVotes(movieId);
    }
}

Test the application

With those pieces in place, you can now test your changes. Run the application and execute the following command in the terminal to see the results.

shell
curl -X PUT 'localhost:8080/movies/updateVotes?movieId=9876'
UpdateVotes results (example)
{
    "movieId":"9876",
    "title":"MyMovie",
    "plot":null,
    "poster":null,
    "url":null,
    "imdbId":null,
    "tmdbId":null,
    "released":null,
    "year":null,
    "runtime":null,
    "budget":null,
    "revenue":null,
    "imdbVotes":1,
    "imdbRating":null,
    "languages":null,
    "countries":null,
    "actors":[]
}

The output from the command should display the updated imdbVotes field. If you execute the command multiple times, you should see the value increment each time.

You can verify the results in the database by running the provided query in the right-hand tab.

Lesson Summary

In this challenge, you used your knowledge to create a custom method with a Cypher statement to write data to the database.

Next, you will learn about projections and how to use them to to retrieve or manipulate custom subsets of domain classes.