documentation updates

This commit is contained in:
Michael Hunger
2011-08-01 13:55:18 +02:00
parent 367da4e96a
commit 1c9cedbcff
16 changed files with 55 additions and 39 deletions

View File

@@ -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

View File

@@ -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).

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

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

View File

@@ -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>

View File

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

View File

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

View File

@@ -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.

View File

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

View File

@@ -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&lt;Role&gt;, 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>

View File

@@ -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>

View File

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

View File

@@ -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>

View File

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