"A human being should be able to change a diaper, plan an invasion, butcher a hog, conn a ship, design a building, write a sonnet, balance accounts, build a wall, set a bone, comfort the dying, take orders, give orders, cooperate, act alone, solve equations, analyze a new problem, pitch manure, program a computer, cook a tasty meal, fight efficiently, die gallantly. Specialization is for insects." (Robert A. Heinlein)

Tuesday, 5 August 2014

Neo4j and Java: demos with an embedded Ne04j graph


After my first experience in installing Neo4j graph database I decided to continue my experiments by writing a little Java demo program. The scope of my program just to learn how to connect to a Neo4j embedded graph, to generate ,connect and query some hundreds of nodes. Neo4j site and the downloaded manual provide plenty of documentation about interfacing with Java, and the other supported languages.

Project set-up

Setting up a Java project is quit simple: just matter of including all Neo4j libraries jars, available in the 'lib' folder, in the project class-path. To make easier future projects set-up I prepared, in Netbeans, a custom library configuration.


Database startup and shutdown

In order to work with a Neo4J database it must be initialized by providing the path where datawill be saved:

protected final static String DBPATH = "./localdb";
protected GraphDatabaseService db = null;
protected void startup()
{
// instantiate the database
db = new GraphDatabaseFactory().newEmbeddedDatabase(DBPATH);

since database startup is a relatively heavy operation documentation warmly suggests to execute this operation only at startup.
As all database operations are concluded, before closing the application, database resources must be released by calling the shutdown method:


db.shutdown();

Documentation suggest attaching a shutdown hook to application, just after database startup, in order to ensure proper database shutdown in every case


// register shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread()
{
@Override
public void run()
{
db.shutdown();
}
});


Last but not least recommendation: all nodes operation must be carried inside a transaction

try (final Transaction t = db.beginTx())
{
….
t.success();
}


Hello → World

The most basic example coming from documentation creates two nodes and links them together

Node n1 = db.createNode();
n1.setProperty("text", "Hello");
Node n2 = db.createNode();
n2.setProperty("text", "World");
Relationship r = n1.createRelationshipTo(n2, DemoRelationship.linked_to);
r.setProperty("text", "to all the");
relationships types are defined as enum types

public enum DemoRelationship implements RelationshipType
{
linked_to
}
Something more …

To go a little further than the usual “hello-world” I decided to write a small demo that filled a graph with a group of random generated “people”. First I made a function that adds to the graph a node representing a person with some random properties:

private Node randomPerson(String family, int generation)
{
Node person = db.createNode(personLabel);
String sex = Math.random()>.5?"M":"F";
String name;
if("F".equals(sex))
{
name = fNames[(int)(Math.random()*fNames.length)];
}
else
{
name = mNames[(int)(Math.random()*mNames.length)];
}
person.setProperty("sex", sex);
person.setProperty("name", name);
person.setProperty("family", family);
person.setProperty("generation", generation);
return person;
}

then I defined a first population pass where I filled the graph with an initial group (generation “0”) of random people. I used a “root” node as a marker to avoid repeating this step more than once.


ResourceIterable<Node> gotRoot = db.findNodesByLabelAndProperty(rootLabel, "type", "root");
if(gotRoot==null || !gotRoot.iterator().hasNext())
{
Node root = db.createNode(rootLabel);
root.setProperty("type", "root");
// initial database fill
for(int j=0;j<INITIAL_FILL;j++)
{
Node person = randomPerson();
person.createRelationshipTo(root, PersonRelationships.initial_setup);
}
t.success();
result = root;
}
else
{
result = gotRoot.iterator().next();
}
 
In each “generation” iteration I selected two random group of “nodes”, filtered by sex, from the previous generation using the following Cypher query.

private static final String GENERATION_QUERY = "match (n:person) "
+ "where not (n-[:married_with]->(:person)) "
+ "and n.sex={sex} "
+ "and rand()>{pick} "
+ "and n.generation={generation} "
+ "return n;";

the nodes from the two sets are then linked together and some “child” nodes are then created

// mate some of same level nodes
Node male = (Node) males.columnAs("n").next();
Node female = (Node) females.columnAs("n").next();
male.createRelationshipTo(female, PersonRelationships.married_with);
// add random children to some random mated nodes
if(Math.random()>BIRTH_RATIO)
{
final double children = Math.random()*MAX_CHLDREN;
for(int j=1;j<children;j++)
{
Node c = randomPerson((String) male.getProperty("family"), generation + 1);
c.createRelationshipTo(male, PersonRelationships.child_of);
c.createRelationshipTo(female, PersonRelationships.child_of);
}
}

Conclusions

After playing a little with birth ratio and other parameters I managed to fill the graph with some hundreds of widely connected nodes. The graph is not a realistic representation of a real population ,since I should have considered many more constraints, but is still an interesting starting point. The full code can be downloaded from here.

No comments :

Post a Comment