After having put some data in the graph database, we also wanted to show it to the user. Adding
the controller method to show a single movie with its attributes and cast in a JSP was
straightforward. It basically just involved using the repository to look the movie up and add
it to the model, and then forwarding to the /movies/show
view and voilá.
Example 11.1. Controller for showing movies
@RequestMapping(value = "/movies/{movieId}", method = RequestMethod.GET, headers = "Accept=text/html") public String singleMovieView(final Model model, @PathVariable String movieId) { Movie movie = repository.findById(movieId); model.addAttribute("id", movieId); if (movie != null) { model.addAttribute("movie", movie); model.addAttribute("stars", movie.getStars()); } return "/movies/show"; }
Example 11.2. Populating the database - JSP /movies/show
<%@ page session="false" %> <%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:choose> <c:when test="${not empty movie}"> <h2>${movie.title} (${stars} Stars)</h2> <c:if test="${not empty movie.roles}"> <ul> <c:forEach items="${movie.roles}" var="role"> <li> <a href="/actors/${role.actor.id}"><c:out value="${role.actor.name}" /> as <c:out value="${role.name}" /></a><br/> </li> </c:forEach> </ul> </c:if> </c:when> <c:otherwise> No Movie with id ${id} found! </c:otherwise> </c:choose>
The UI had now evolved to this:
The next thing was to allow users to search for movies, so we needed some fulltext search capabilities. As the default index provider implementation of Neo4j is based on Apache Lucene, we were delighted to see that fulltext indexes were supported out of the box.
We happily annotated the title field of the Movie class with @Indexed(type = FULLTEXT)
.
Next thing we got an exception telling us that we had to specify a separate index name.
So we simply changed it to @Indexed(type = FULLTEXT, indexName = "search")
.
With derived finder methods, finding things became easy. By simply declaring a finder-method name that expressed
the required properties, it worked without annotations.
Cool stuff and you could even tell it that it should return pages of movies, its size and offset specified by a
Pageable
which also contains sort information. Using the like
operator indicates
that fulltext search should be used, instead of an exact search.
Example 11.3. Searching for movies
public interface MovieRepository ... { Movie findById(String id); Page<Movie> findByTitleLike(String title, Pageable page); }
We then used this result in the controller to render a page of movies, driven by a search box. The movie properties and the cast were accessible through the getters in the domain classes.
Example 11.4. Search controller
@RequestMapping(value = "/movies", method = RequestMethod.GET, headers = "Accept=text/html") public String findMovies(Model model, @RequestParam("q") String query) { Page<Movie> movies = repository.findByTitleLike(query, new PageRequest(0,20)); model.addAttribute("movies", movies); model.addAttribute("query", query); return "/movies/list"; }
Example 11.5. Search Results JSP
<h2>Movies</h2> <c:choose> <c:when test="${not empty movies}"> <dl class="listings"> <c:forEach items="${movies}" var="movie"> <dt> <a href="/movies/${movie.id}"><c:out value="${movie.title}" /></a><br/> </dt> <dd> <c:out value="${movie.description}" escapeXml="true" /> </dd> </c:forEach> </dl> </c:when> <c:otherwise> No movies found for query "${query}". </c:otherwise> </c:choose>
The UI now looked like this: