Interface-based projections are a great place to start. They are simple to create and commonly used when you want to trim down an entity for user views or retrieval.
Take the Movie
entity as an example. There are sixteen fields on the entity, which is a lot to work with!
@Node
public class Movie {
@Id
private String movieId;
private String title;
private String plot;
private String poster;
private String url;
private String imdbId;
private String tmdbId;
private String released;
private Long year;
private Long runtime;
private Long budget;
private Long revenue;
private Long imdbVotes;
private Double imdbRating;
private String[] languages;
private String[] countries;
//constructor, getters, and setters
}
When you saved a new movie back in Module 4, you only sent a few values, making the return results show a lot of null
values. What if more of the data contained empty values or you wanted to provide a page for users to scroll through all movies without displaying every field?
You could write a projection to only include a few key fields. Next, you will create a projection that only contains the title
, released
, and poster
properties for each movie.
MovieProjection interface
To create a projection, create an interface called MovieProjection.java
in the src/main/java/com/example/appspringdata
folder. Then call the getter methods of the desired properties for title
, released
, and poster
.
public interface MovieProjection {
String getTitle();
String getReleased();
String getPoster();
}
Now add a new method to each of the MovieRepository
and MovieController
files to return a list of MovieProjection
instead of Movie
with an endpoint mapping of /movielist
.
interface MovieRepository extends Neo4jRepository<Movie, String> {
//other methods
Iterable<MovieProjection> findAllMovieProjectionsBy();
}
@RestController
@RequestMapping("/movies")
public class MovieController {
//other methods
@GetMapping("/movielist")
Iterable<MovieProjection> findAllMovieProjections() {
return movieRepo.findAllMovieProjectionsBy();
}
}
Completed repository and controller code is available in the dropdown below.
Click to reveal the completed code
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);
Iterable<MovieProjection> findAllMovieProjectionsBy();
}
@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);
}
@GetMapping("/movielist")
Iterable<MovieProjection> findAllMovieProjections() { return movieRepo.findAllMovieProjectionsBy(); }
}
Returning a projection from a repository method
As mentioned in the documentation, the return type of the method (MovieProjection
) is different from the repository’s domain type (Movie
), and therefore must use properties defined in the domain type. The method’s suffix By
is needed to make SDN not look for a property called MovieProjections
in the Movie
class.
Now, when you test the application and call the /movies/movielist
endpoint, you will only get the three properties specified in the projection.
curl 'localhost:8080/movies/movielist'
{
"title":"The Beatles: Eight Days a Week - The Touring Years",
"poster":"https://image.tmdb.org/t/p/w440_and_h660_face/A6q7Jy4vXgXXoCoHX4lpCaKvcMV.jpg",
"released":null
}
Lesson Summary
In this lesson, you learned how to create an interface projection to return a subset of properties from an entity.
In the next optional lesson, you will learn how to create a DTO-based projection for the Movie
entity.