Experiment #3
Now that I know neo4j can handle my needs as a developer, I'm looking for a higher-level abstraction so I don't have to deal with every little persistence detail in code.
Enter jo4neo - a persistence annotation layer that runs on top of POJOs or any other domain object. Time to test that out.
I created a few really simple domain objects: Artists, Albums, and Songs. Here are the simplified versions of those classes:
/*..package and import statements here..*/
public class Artist {
private transient Nodeid neoid;
@neo(index=true) private String id;
@neo (index=true) private String name;
@neo("album") private Collection<Album> albums;
/*..getters and setters here*/
}
/*..package and import statements here..*/
public class Album {
private transient Nodeid neoid;
@neo(index = true)
private String id;
@neo(index = true)
private String name;
@neo("song")
private Collection<Song> songs;
/*..getters and setters here*/
}
/*..package and import statements here..*/
public class Song {
private transient Nodeid neoid;
@neo(index = true)
private String id;
@neo(index = true)
private String name;
/*..getters and setters here*/
}
Notice how simple the annotations are. The only real intrusion on my domain object is the presence of the nodeid
variable. The annotations determine whether fields are indexed, what their names are in the database, etc. Very simple.
So how about a little test? 30 artists, 4 albums each, 11 songs each.
/* Jo4NeoPerformanceExercise
* Chris Freyer
* Chris@TheFreyers.net
* 7/23/2010
*/
package com.freyertech.jo4neotest.model;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Calendar;
import jo4neo.ObjectGraph;
import jo4neo.ObjectGraphFactory;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.kernel.EmbeddedGraphDatabase;
public class Jo4NeoPerformanceExercise {
private static int NUM_ARTISTS = 30;
private static int NUM_ALBUMS = 4;
private static int NUM_SONGS = 11;
public static void main(String[] args) {
long starttime = Calendar.getInstance().getTimeInMillis();
GraphDatabaseService neo = new EmbeddedGraphDatabase("neo_store2");
ObjectGraph graph = ObjectGraphFactory.instance().get(neo);
try {
setup(graph);
queryDB(graph);
cleanDB(graph);
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
e.printStackTrace();
} finally {
graph.close();
neo.shutdown();
}
long endtime = Calendar.getInstance().getTimeInMillis();
output("Runtime: " + (endtime - starttime) + "ms.");
}
private static void setup(ObjectGraph graph) {
output("Setting up test data...");
//add new artists in loop
for (int i = 0; i < NUM_ARTISTS; i++) {
Artist art = new Artist();
art.setName("Artist #" + i);
art.setId("" + i);
//add new albums for each artist
for (int j = 0; j < NUM_ALBUMS; j++) {
Album al = new Album();
al.setId("" + i + "_" + j);
al.setName("Album #" + j);
art.getAlbums().add(al);
//add new songs for each album;
for (int k = 0; k < NUM_SONGS; k++) {
Song s = new Song();
s.setId("" + i + "_" + j + "_" + k);
s.setName("Song #" + k);
al.getSongs().add(s);
}
}
graph.persist(art);
}
output("done");
}
private static void output(String s) {
System.out.println(s + "\n");
}
private static void cleanDB(ObjectGraph graph) {
output("Deleting records...");
for (Artist art : graph.get(Artist.class)) {
graph.delete(art);
}
output("done.");
}
private static void queryDB(ObjectGraph graph) {
output("Walking through objects...");
String format = "|%1$-12s|%2$-12s|%3$-12s|\n";
for (Artist artist : graph.get(Artist.class)) {
for (Album album : artist.getAlbums()) {
for (Song song : album.getSongs()) {
System.out.format(format, artist.getName(), album.getName(),song.getName());
}
}
}
}
}
And here's the output:
run:
Setting up test data...
done
Walking through objects...
|Artist #29 |Album #0 |Song #0 |
|Artist #29 |Album #0 |Song #1 |
|Artist #29 |Album #0 |Song #2 |
|Artist #29 |Album #0 |Song #3 |
(...lots of output deleted here for brevity...)
|Artist #0 |Album #3 |Song #9 |
|Artist #0 |Album #3 |Song #10 |
Deleting records...
done.
Runtime: 56672ms.
Summary
I was able to startup the database; create, query, and delete 1,320 business objects; and shutdown the database in just under a minute. Nice! But the important thing isn't just the running time. There's a decrease in code complexity as well.
The business objects I created are nothing more than POJOs with annotations. The jo4neo toolkit allows me to stay in the business domain and not be concerned about the database. As my business domain expands, this could have larger implications if I'm able to keep the complexity down. Less complexity = less time = lower cost of development.
Points of Interest
A couple of nice features:
- you don't see ANY persistence code, except for the occasional
graph.delete()
orgraph.persist()
methods when using jo4neo. - the only time I leave POJO mode is when I do a direct search for objects by type (i.e.
graph.get(Artist.class)
) - Neo4J returns Java Collections, which lets me use Java 6 style loops (i.e.
for (Artist artist : graph.get(Artist.class))
) - The entire example (without domain objects) was 113 lines of code
So far I've spent ZERO time creating a database, and far less time than normal on persistence mapping. That leaves far more time than normal for domain object modelling. But I haven't needed (yet).