documentation updates
This commit is contained in:
@@ -37,7 +37,7 @@
|
||||
database instead. Under the hood, properties and relationships will be created.
|
||||
</para>
|
||||
<para>
|
||||
So how is an aspect applied to a concrete class? At compile time , the AspectJ Java compiler (ajc) takes
|
||||
So how is an aspect applied to a concrete class? At compile time, the AspectJ Java compiler (ajc) takes
|
||||
source files and aspect definitions, and compiles the source files while adding all the necessary
|
||||
interception code for the aspects to hook in where they're declared to. This is known as compile-time
|
||||
<emphasis>weaving</emphasis>. At runtime only a small AspectJ runtime is needed, as the byte code of the
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<!--frameworks and libraries that expect Java POJOs as input or create them as results.-->
|
||||
<!--</para>-->
|
||||
<para>
|
||||
Spring Data Graph was however not designed with a major focus on performance. It does add some overhead
|
||||
Spring Data Graph, however, was not designed with a major focus on performance. It does add some overhead
|
||||
to pure graph operations. Something to keep in mind is that any access of properties and relationships
|
||||
will in general read through down to the database. To avoid multiple reads, it is sensible to store the
|
||||
result in a local variable in suitable scope (e.g. method, class or jsp).
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
to nodes, relationships, and properties in the graph database.
|
||||
</para>
|
||||
<para>
|
||||
Spring Data Graph allows anytime to drop down to the Neo4j-API level to execute functionality with
|
||||
Spring Data Graph allows, at anytime, to drop down to the Neo4j-API level to execute functionality with
|
||||
the highest performance possible. For Integration of Neo4j and Grails/GORM please refer to the Neo4j
|
||||
<ulink url="http://www.grails.org/plugin/neo4j">grails plugin</ulink>.
|
||||
</para>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<title>AspectJ IDE support</title>
|
||||
<para>
|
||||
As Spring Data Graph uses some advanced features of AspectJ, users may experience issues with
|
||||
their IDE reporting errors where there in fact are none. Features that might be reported wrongfully
|
||||
their IDE reporting errors where in fact there are none. Features that might be reported wrongfully
|
||||
include: introduction of methods to interfaces, declaration of additional interfaces for annotated
|
||||
classes, and generified introduced methods.
|
||||
</para>
|
||||
|
||||
@@ -28,7 +28,8 @@ springVersion = "3.0.5.RELEASE"
|
||||
springDataGraphVersion = "1.1.0.M2"
|
||||
aspectjVersion = "1.6.12.M1
|
||||
|
||||
apply from:'https://github.com/SpringSource/spring-data-graph/raw/master/build/gradle/springdatagraph.gradle'
|
||||
apply from:'https://github.com/SpringSource/spring-data-graph/raw/master/build/
|
||||
gradle/springdatagraph.gradle'
|
||||
|
||||
configurations {
|
||||
runtime
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
Property fields were taken care of automatically.
|
||||
</para>
|
||||
<para>
|
||||
It was time to put our entities to a test. How could we now be assured that a attribute really was persisted to
|
||||
It was time to put our entities to a test. How could we now be assured that an attribute really was persisted to
|
||||
the graph store? We wanted to load the entity and check the attribute.
|
||||
|
||||
Either we could have a GraphDatabaseContext injected and use its <code>getById(entityId)</code>
|
||||
@@ -48,12 +48,11 @@
|
||||
need external transactions, but not for this simple test.
|
||||
</para>
|
||||
<para>
|
||||
Our domain model had now evolved.
|
||||
<example>
|
||||
<title>Movie class</title>
|
||||
<title>Movie class with annotation</title>
|
||||
<programlisting language="java"><![CDATA[@NodeEntity
|
||||
class Movie {
|
||||
int id;
|
||||
String id;
|
||||
String title;
|
||||
int year;
|
||||
Set<Role> cast;
|
||||
|
||||
@@ -64,6 +64,7 @@ class Rating {
|
||||
</example>
|
||||
</para>
|
||||
<para>
|
||||
Then we wrote some tests to show how the basic plumbing works.
|
||||
Then we wrote some simple tests to show that the basic design of the domain is good enough so far. Just creating
|
||||
a movie populating it with actors and having it rated by a user and its friends.
|
||||
</para>
|
||||
</chapter>
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
"status":"Released",
|
||||
"tagline":"One Witness. One Camera",
|
||||
"certification":"R",
|
||||
"overview":"\"REC\" turns on a young TV reporter and her cameraman who cover the night shift at the local fire station...
|
||||
"keywords":["terror", "lebende leichen", "obsession", "camcorder", "firemen", "reality tv ", "bite", "cinematographer",
|
||||
"overview":"\"REC\" turns on a young TV reporter and her cameraman who cover the night shift
|
||||
at the local fire station...
|
||||
"keywords":["terror", "lebende leichen", "obsession", "camcorder", "firemen", "reality tv ",
|
||||
"bite", "cinematographer",
|
||||
"attempt to escape", "virus", "lodger", "live-reportage", "schwerverletzt"],
|
||||
"released":"2007-08-29",
|
||||
"runtime":78,
|
||||
@@ -39,14 +41,15 @@
|
||||
"countries":[{"code":"ES", "name":"Spain", "url":"http://www.themoviedb.org/country/es"}],
|
||||
"posters":[{"image":{"type":"poster",
|
||||
"size":"original", "height":1000, "width":706,
|
||||
"url":"http://cf1.imgobject.com/posters/3a0/4cc8df415e73d650240003a0/rec-original.jpg", "id":"4cc8df415e73d650240003a0"}},
|
||||
"url":"http://cf1.imgobject.com/posters/3a0/4cc8df415e73d650240003a0/rec-original.jpg",
|
||||
"id":"4cc8df415e73d650240003a0"}},
|
||||
....
|
||||
"cast":[{"name":"Manuela Velasco",
|
||||
"job":"Actor", "department":"Actors",
|
||||
"character":"Angela Vidal",
|
||||
"id":34793, "order":0, "cast_id":1,
|
||||
"url":"http://www.themoviedb.org/person/34793",
|
||||
"profile":"http://cf1.imgobject.com/profiles/390/4c0157fa017a3c702d001390/manuela-velasco-thumb.jpg"},
|
||||
"profile":"http://cf1.imgobject.com/profiles/390/.../manuela-velasco-thumb.jpg"},
|
||||
...
|
||||
{"name":"Gl\u00f2ria Viguer",
|
||||
"job":"Costume Design", "department":"Costume \u0026 Make-Up",
|
||||
@@ -73,7 +76,7 @@
|
||||
"character":"The Frankenstein Monster",
|
||||
"cast_id":23,
|
||||
"url":"http://www.themoviedb.org/movie/3073",
|
||||
"poster":"http://cf1.imgobject.com/posters/4ca/4bc9185d017a3c57fe0094ca/bud-abbott-lou-costello-meet-frankenstein-cover.jpg",
|
||||
"poster":"http://cf1.imgobject.com/posters/4ca/.../bud-abbott-lou-costello-meet-frankenstein-cover.jpg",
|
||||
"adult":false, "release":"1948-06-15"},
|
||||
...],
|
||||
"profile":[],
|
||||
@@ -102,7 +105,8 @@ public Movie importMovie(String movieId) {
|
||||
}
|
||||
|
||||
Map data = loadMovieData(movieId);
|
||||
if (data.containsKey("not_found")) throw new RuntimeException("Data for Movie "+movieId+" not found.");
|
||||
if (data.containsKey("not_found"))
|
||||
throw new RuntimeException("Data for Movie "+movieId+" not found.");
|
||||
movieDbJsonMapper.mapToMovie(data, movie);
|
||||
movie.persist();
|
||||
relatePersonsToMovie(movie, data);
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
@Test public void persistedMovieShouldBeRetrievableFromGraphDb() {
|
||||
int id = 1;
|
||||
Movie forrestGump = new Movie(id, "Forrest Gump", 1994).persist();
|
||||
GraphRepository<Movie> movieRepository = graphRepositoryFactory.createGraphRepository(Movie.class);
|
||||
GraphRepository<Movie> movieRepository =
|
||||
graphRepositoryFactory.createGraphRepository(Movie.class);
|
||||
Movie retrievedMovie = movieRepository.findByPropertyValue("id", id);
|
||||
assertEqual("retrieved movie matches persisted one", forrestGump, retrievedMovie);
|
||||
assertEqual("retrieved movie title matches", "Forrest Gump", retrievedMovie.getTitle());
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<ulink url="http://wiki.neo4j.org/content/Traversal_Framework">Traversal Description</ulink>.
|
||||
</para>
|
||||
<para>
|
||||
We also learned that Neo4j is fully transactional and therefore upholds ACID guarantees for our data.
|
||||
We also learned that Neo4j is fully transactional and therefore upholds <ulink url="http://en.wikipedia.org/wiki/ACID">ACID</ulink> guarantees for our data.
|
||||
This is unusual for NOSQL databases, but easier for us to get our head around than non-transactional
|
||||
eventual consistency. It also made us feel safe, though it also meant that we had to manage transactions.
|
||||
Something to keep in mind for later.
|
||||
|
||||
@@ -24,21 +24,26 @@
|
||||
final DynamicRelationshipType RATED = withName(User.RATED);
|
||||
final Map<Long,int[]> ratings=new HashMap<Long, int[]>();
|
||||
TraversalDescription traversal= Traversal.description().breadthFirst()
|
||||
.relationships(withName(User.FRIEND)).relationships(RATED, OUTGOING).evaluator(new Evaluator() {
|
||||
.relationships(withName(User.FRIEND)).relationships(RATED, OUTGOING)
|
||||
.evaluator(new Evaluator() {
|
||||
|
||||
public Evaluation evaluate(Path path) {
|
||||
final int length = path.length() - 1;
|
||||
if (length > ratingDistance) return Evaluation.EXCLUDE_AND_PRUNE; // only as far as requested
|
||||
// only as far as requested
|
||||
if (length > ratingDistance) return Evaluation.EXCLUDE_AND_PRUNE;
|
||||
Relationship rating = path.lastRelationship();
|
||||
if (rating != null && rating.getType().equals(RATED)) { // process RATED relationships, not FRIEND
|
||||
if (length == 0) return Evaluation.EXCLUDE_AND_PRUNE; // my rated movies
|
||||
// process RATED relationships, not FRIEND
|
||||
if (rating != null && rating.getType().equals(RATED)) {
|
||||
// my rated movies
|
||||
if (length == 0) return Evaluation.EXCLUDE_AND_PRUNE;
|
||||
final long movieId = rating.getEndNode().getId();
|
||||
int[] stars = ratings.get(movieId);
|
||||
if (stars == null) {
|
||||
stars = new int[2];
|
||||
ratings.put(movieId, stars);
|
||||
}
|
||||
int weight = ratingDistance - length; // aggregate for averaging, inverse to distance
|
||||
// aggregate for averaging, inverse to distance
|
||||
int weight = ratingDistance - length;
|
||||
stars[0] += weight * (Integer) rating.getProperty("stars", 0);
|
||||
stars[1] += weight;
|
||||
return Evaluation.INCLUDE_AND_PRUNE;
|
||||
@@ -48,7 +53,8 @@
|
||||
});
|
||||
|
||||
Map<Movie,Integer> result=new HashMap<Movie, Integer>();
|
||||
final Iterable<Movie> movies = movieRepository.findAllByTraversal(user, traversal); // lazy traversal results
|
||||
// lazy traversal results
|
||||
final Iterable<Movie> movies = movieRepository.findAllByTraversal(user, traversal);
|
||||
for (Movie movie : movies) { // assign movie to averaged rating
|
||||
final int[] stars = ratings.get(movie.getNodeId());
|
||||
result.put(movie, stars[0]/stars[1]);
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<title>Relationships</title>
|
||||
<subtitle>A convincing act</subtitle>
|
||||
<para>
|
||||
Our application was not yet very much fun, just storing movies and actors. After all,
|
||||
the power is in the relationships between them. Fortunately Neo4j treats relationships as first class citizens
|
||||
Our application was not yet very much fun yet, just storing movies and actors. After all,
|
||||
the power is in the relationships between them. Fortunately, Neo4j treats relationships as first class citizens,
|
||||
allowing them to be addressed individually and assigned properties. That allows for representing them as entities
|
||||
if needed.
|
||||
</para>
|
||||
@@ -64,7 +64,7 @@ class Role {
|
||||
<title>Accessing related entities</title>
|
||||
<para>
|
||||
Now we wanted to find connected entities.
|
||||
We already had fields for the relationships in both classes. Now it was time to annotate them
|
||||
We already had fields for the relationships in both classes. It was time to annotate them
|
||||
correctly. It turned out that we needed to provide the target type of the fields again, due to
|
||||
Java's type erasure. The Neo4j relationship type and direction were easy to figure out.
|
||||
The direction even defaulted to outgoing, so we only had to specify it for the movie.
|
||||
@@ -112,9 +112,9 @@ class Actor {
|
||||
<title>Accessing the relationship entities</title>
|
||||
<para>
|
||||
But we still couldn't access the Role relationships. It turned out that there was a separate
|
||||
annotation <code>@RelatedToVia</code> for accessing the actual relationship entities . And we had
|
||||
annotation <code>@RelatedToVia</code> for accessing the actual relationship entities. And we had
|
||||
to declare the field as an Iterable<Role>, with read-only semantics. This appeared to mean
|
||||
that we were not able to add new roles though the field. Adding relationship entities seemed like
|
||||
that we were not able to add new roles through the field. Adding relationship entities seemed like
|
||||
it had to be done by using <code>entity.relateTo()</code>. The annotation attributes were similar to
|
||||
those used for <code>@RelatedTo</code>. So off we went, creating our first real relationship (just kidding).
|
||||
</para>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<para>
|
||||
Before we opened the gates we needed to add some movie data. So we wrote a small class for
|
||||
populating the database which could be called from our controller. To make it safe to call
|
||||
several times we added index lookups to check for existing entries. A simple /populate endpoint
|
||||
several times we added index lookups to check for existing entries. A simple <code>/populate</code> endpoint
|
||||
for the controller that called it would be enough for now.
|
||||
</para>
|
||||
<para>
|
||||
@@ -64,7 +64,8 @@ public class MovieController {
|
||||
<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/>
|
||||
<a href="/actors/${role.actor.id}"><c:out value="${role.actor.name}" /> as
|
||||
<c:out value="${role.name}" /></a><br/>
|
||||
</li>
|
||||
</c:forEach>
|
||||
</ul>
|
||||
@@ -82,7 +83,7 @@ public class MovieController {
|
||||
</para>
|
||||
<note>
|
||||
Pardon the misused GET parameter for that (don't try this at home, the
|
||||
<ulink url="http://restinpractice.com">REST guys</ulink> will be hunt you down). This is only for
|
||||
<ulink url="http://restinpractice.com">REST guys</ulink> will hunt you down). This is only for
|
||||
running it from the browser address line. The next iteration of this website would use a button
|
||||
with POST.
|
||||
</note>
|
||||
|
||||
@@ -92,7 +92,8 @@ public class CineastsUserDetailsService implements UserDetailsService, Initializ
|
||||
@Autowired private UserRepository userRepository;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException, DataAccessException {
|
||||
public UserDetails loadUserByUsername(String login)
|
||||
throws UsernameNotFoundException, DataAccessException {
|
||||
final User user = findUser(login);
|
||||
if (user==null) throw new UsernameNotFoundException("Username not found",login);
|
||||
return new CineastsUserDetails(user);
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<para>
|
||||
So far it had all been pure Spring Framework and Neo4j. However, using the Neo4j code in our domain
|
||||
classes polluted them with graph database details. For this application, we wanted to keep the domain
|
||||
classes clean. Spring Data Graph promised to do the heavy lifting for us, so we continued with
|
||||
investigating it.
|
||||
classes clean. Spring Data Graph promised to do the heavy lifting for us, so we continued investigating it.
|
||||
</para>
|
||||
<para>
|
||||
Spring Data Graph depends heavily on AspectJ weaving. Some parts of our classes would get new behavior,
|
||||
Spring Data Graph depends heavily on AspectJ <xref linkend="reference:aspectj-details">features</xref>.
|
||||
Some parts of our classes would get new behavior,
|
||||
but it would not be visible in our code. The upside of this is that you get rid of a lot of boilerplate
|
||||
code.
|
||||
</para>
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
<para>
|
||||
<example>
|
||||
<title>Controller for showing movies</title>
|
||||
<programlisting language="java"><![CDATA[@RequestMapping(value = "/movies/{movieId}", method = RequestMethod.GET, headers = "Accept=text/html")
|
||||
<programlisting language="java"><![CDATA[@RequestMapping(value = "/movies/{movieId}",
|
||||
method = RequestMethod.GET, headers = "Accept=text/html")
|
||||
public String singleMovieView(final Model model, @PathVariable String movieId) {
|
||||
Movie movie = repository.getMovie(movieId);
|
||||
model.addAttribute("id", movieId);
|
||||
@@ -37,7 +38,7 @@ public String singleMovieView(final Model model, @PathVariable String movieId) {
|
||||
<title>Searching</title>
|
||||
<para>
|
||||
The next thing was to allow users to search for movies, so we needed some fulltext search
|
||||
capabilities. As the index provider implementation of Neo4j is based on Apache Lucene,
|
||||
capabilities. As the index provider implementation of Neo4j is based on <ulink href="http://lucene.apache.org/java/docs/index.html">Apache Lucene</ulink>,
|
||||
we were delighted to see that fulltext indexes were supported out of the box.
|
||||
</para>
|
||||
<para>
|
||||
@@ -77,7 +78,8 @@ public String singleMovieView(final Model model, @PathVariable String movieId) {
|
||||
<para>
|
||||
<example>
|
||||
<title>Search controller</title>
|
||||
<programlisting language="java"><![CDATA[@RequestMapping(value = "/movies", method = RequestMethod.GET, headers = "Accept=text/html")
|
||||
<programlisting language="java"><![CDATA[@RequestMapping(value = "/movies",
|
||||
method = RequestMethod.GET, headers = "Accept=text/html")
|
||||
public String findMovies(Model model, @RequestParam("q") String query) {
|
||||
List<Movie> movies = repository.findMovies(query, 20);
|
||||
model.addAttribute("movies", movies);
|
||||
|
||||
Reference in New Issue
Block a user