Often in the life of developer’s life there is a scenario where using a relational database tends to get complicated or sometimes even slow – especially when there are fragments with multiple relationships or multiple connections present. This often leads to complex database queries or desperate software engineers trying to handle those problems with their ORM framework.
A possible solution might be to switch from a relational database to a graph database – and – neo4j is our tool of choice here. In the following tutorial we’re going to implement several examples to demonstrate the strengths of a graph database .. from a route planner to a social graph.
Prerequisites
You need to meet the following requirements to run the samples below ..
Adding neo4j to a Maven Project
First a new Maven project …
-
Create a new simple Maven project using your IDE or
mvn archetype:generate
-
Add the following dependencies needed for neo4j
<dependency> <groupId>org.neo4j</groupId> <artifactId>neo4j</artifactId> <version>1.5</version> </dependency>
-
The neo4j community edition is licensed under the GPLv3 license
Example: Using the Indexer
Often we want to search for a node with a specific property e.g. an id or another attribute without having to traverse the node graph. Luckily for us neo4j comes with my favourite indexer, Lucene and allows to find a node in no time by searching for its indexed properties.
In the following example, we’re creating two nodes – each node has its properties name and id – and we’re adding those properties to the Lucene index so that we’re able to search for these properties..
This is my sample named IndexSearchExample
package com.hascode.tutorial;
import static com.hascode.tutorial.GraphUtil.cleanUp;
import static com.hascode.tutorial.GraphUtil.registerShutdownHook;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.index.Index;
import org.neo4j.kernel.EmbeddedGraphDatabase;
public class IndexSearchExample {
private static String DB_PATH = "/tmp/neo4j";
public static void main(final String[] args) {
GraphDatabaseService graphDb = new EmbeddedGraphDatabase(DB_PATH);
Index<Node> nodeIndex = graphDb.index().forNodes("nodes");
registerShutdownHook(graphDb);
Transaction tx = graphDb.beginTx();
try {
// cleanup first for this tutorial
cleanUp(graphDb, nodeIndex);
Node userNode1 = graphDb.createNode();
userNode1.setProperty("id", 1);
userNode1.setProperty("name", "Peter");
nodeIndex.add(userNode1, "id", 1);
nodeIndex.add(userNode1, "name", "Peter");
Node userNode2 = graphDb.createNode();
userNode2.setProperty("id", 2);
userNode2.setProperty("name", "Ray");
nodeIndex.add(userNode2, "id", 2);
nodeIndex.add(userNode2, "name", "Ray");
tx.success();
System.out.println("searching for user with id=2..");
Node user = nodeIndex.get("id", 2).getSingle();
System.out.println("The name of the user with id=2 is: "
+ user.getProperty("name"));
System.out.println("searching for user with name=Peter..");
Node user2 = nodeIndex.get("name", "Peter").getSingle();
System.out.println("The id of the user with name=Peter is: "
+ user2.getProperty("id"));
} finally {
tx.finish();
graphDb.shutdown();
}
}
}
I’ve put two convenience methods for this tutorial in a utility class named GraphUtil – it’s just to recreate the node graph and the index on every run. I’m using it in the other examples, too..
package com.hascode.tutorial;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.index.Index;
public class GraphUtil {
public static void cleanUp(final GraphDatabaseService graphDb,
final Index<Node> nodeIndex) {
for (Node node : graphDb.getAllNodes()) {
for (Relationship rel : node.getRelationships()) {
rel.delete();
}
nodeIndex.remove(node);
node.delete();
}
}
public static void registerShutdownHook(final GraphDatabaseService graphDb) {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
graphDb.shutdown();
}
});
}
}
Running the example you should see an output similar to this:
searching for user with id=2..
The name of the user with id=2 is: Ray
searching for user with name=Peter..
The id of the user with name=Peter is: 1
Example: Node Traversal
In the next example we want to create – and link some nodes and afterwards traverse them. Nodes with two properties id and name may have a relationship of type “KNOWS”.
A graph of people knowing each other image::people-graph-relations.png[]
I have created an enum named RelTypes that implements RelationshipType. This is where the relations are defined
package com.hascode.tutorial;
import org.neo4j.graphdb.RelationshipType;
public enum RelTypes implements RelationshipType {
KNOWS, LEADS_TO
}
This is my sample class: NodeTraversal
package com.hascode.tutorial;
import static com.hascode.tutorial.GraphUtil.registerShutdownHook;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.ReturnableEvaluator;
import org.neo4j.graphdb.StopEvaluator;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.Traverser;
import org.neo4j.graphdb.Traverser.Order;
import org.neo4j.kernel.EmbeddedGraphDatabase;
public class NodeTraversalExample {
private static String DB_PATH = "/tmp/neo4j";
public static void main(final String[] args) {
GraphDatabaseService graphDb = new EmbeddedGraphDatabase(DB_PATH);
registerShutdownHook(graphDb);
Transaction tx = graphDb.beginTx();
try {
Node peterNode = graphDb.createNode();
peterNode.setProperty("id", 1);
peterNode.setProperty("name", "Peter");
Node rayNode = graphDb.createNode();
rayNode.setProperty("id", 2);
rayNode.setProperty("name", "Ray");
Node egonNode = graphDb.createNode();
egonNode.setProperty("id", 3);
egonNode.setProperty("name", "Egon");
Node winstonNode = graphDb.createNode();
winstonNode.setProperty("id", 4);
winstonNode.setProperty("name", "Winston");
Node slimerNode = graphDb.createNode();
slimerNode.setProperty("id", 5);
slimerNode.setProperty("name", "Slimer");
Relationship rel1 = peterNode.createRelationshipTo(rayNode,
RelTypes.KNOWS);
rel1.setProperty("visibility", "public");
Relationship rel2 = rayNode.createRelationshipTo(egonNode,
RelTypes.KNOWS);
rel2.setProperty("visibility", "hidden");
Relationship rel3 = rayNode.createRelationshipTo(winstonNode,
RelTypes.KNOWS);
rel3.setProperty("visibility", "public");
Relationship rel4 = winstonNode.createRelationshipTo(slimerNode,
RelTypes.KNOWS);
rel4.setProperty("visibility", "public");
tx.success();
System.out.println("traversing nodes for Peter's acquaintances..");
Traverser acquaintanceTraverser = getAcquaintances(peterNode);
for (Node acquaintanceNode : acquaintanceTraverser) {
System.out.println("Peter knows "
+ acquaintanceNode.getProperty("name") + " (id: "
+ acquaintanceNode.getProperty("id") + ") at depth: "
+ acquaintanceTraverser.currentPosition().depth());
}
} finally {
tx.finish();
}
graphDb.shutdown();
}
private static Traverser getAcquaintances(final Node personNode) {
return personNode.traverse(Order.BREADTH_FIRST,
StopEvaluator.END_OF_GRAPH,
ReturnableEvaluator.ALL_BUT_START_NODE, RelTypes.KNOWS,
Direction.OUTGOING);
}
}
Running the sample, the following output should appear
traversing nodes for Peter's acquaintances..
Peter knows Ray (id: 2) at depth: 1
Peter knows Egon (id: 3) at depth: 2
Peter knows Winston (id: 4) at depth: 2
Peter knows Slimer (id: 5) at depth: 3
Example: Node Filtering
In the following example we’re traversing the nodes given some additional criteria – only relations with a property visibility=public should be traversed..
This is my example – FilteredNodeTraversalExample
package com.hascode.tutorial;
import static com.hascode.tutorial.GraphUtil.registerShutdownHook;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.ReturnableEvaluator;
import org.neo4j.graphdb.StopEvaluator;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.TraversalPosition;
import org.neo4j.graphdb.Traverser;
import org.neo4j.graphdb.Traverser.Order;
import org.neo4j.kernel.EmbeddedGraphDatabase;
public class FilteredNodeTraversalExample {
private static String DB_PATH = "/tmp/neo4j";
public static void main(final String[] args) {
GraphDatabaseService graphDb = new EmbeddedGraphDatabase(DB_PATH);
registerShutdownHook(graphDb);
Transaction tx = graphDb.beginTx();
try {
Node peterNode = graphDb.createNode();
peterNode.setProperty("id", 1);
peterNode.setProperty("name", "Peter");
Node rayNode = graphDb.createNode();
rayNode.setProperty("id", 2);
rayNode.setProperty("name", "Ray");
Node egonNode = graphDb.createNode();
egonNode.setProperty("id", 3);
egonNode.setProperty("name", "Egon");
Node winstonNode = graphDb.createNode();
winstonNode.setProperty("id", 4);
winstonNode.setProperty("name", "Winston");
Node slimerNode = graphDb.createNode();
slimerNode.setProperty("id", 5);
slimerNode.setProperty("name", "Slimer");
Relationship rel1 = peterNode.createRelationshipTo(rayNode,
RelTypes.KNOWS);
rel1.setProperty("visibility", "public");
Relationship rel2 = rayNode.createRelationshipTo(egonNode,
RelTypes.KNOWS);
rel2.setProperty("visibility", "hidden");
Relationship rel3 = rayNode.createRelationshipTo(winstonNode,
RelTypes.KNOWS);
rel3.setProperty("visibility", "public");
Relationship rel4 = winstonNode.createRelationshipTo(slimerNode,
RelTypes.KNOWS);
rel4.setProperty("visibility", "public");
tx.success();
System.out
.println("traversing nodes for Peter's public acquaintances..");
Traverser acquaintanceTraverser = getAcquaintances(peterNode);
for (Node acquaintanceNode : acquaintanceTraverser) {
System.out.println("Peter knows "
+ acquaintanceNode.getProperty("name") + " (id: "
+ acquaintanceNode.getProperty("id") + ") at depth: "
+ acquaintanceTraverser.currentPosition().depth());
}
} finally {
tx.finish();
}
graphDb.shutdown();
}
private static Traverser getAcquaintances(final Node personNode) {
return personNode.traverse(Order.BREADTH_FIRST,
StopEvaluator.END_OF_GRAPH, new ReturnableEvaluator() {
@Override
public boolean isReturnableNode(
final TraversalPosition currentPos) {
return !currentPos.isStartNode()
&& currentPos.lastRelationshipTraversed()
.hasProperty("visibility")
&& "public".equals(currentPos
.lastRelationshipTraversed()
.getProperty("visibility"));
}
}, RelTypes.KNOWS, Direction.OUTGOING);
}
}
And this is the expected output
traversing nodes for Peter's public acquaintances..
Peter knows Ray (id: 2) at depth: 1
Peter knows Winston (id: 4) at depth: 2
Peter knows Slimer (id: 5) at depth: 3
Example: Building a Route Planner
In the following example we’re simulating a railway network – railway stations are connected with other railway stations and each connection between two stations has a property: the distance (in miles).
We’re using Dijkstras algorithm to calculate the shortest route from London to Bristol and Northampton to Brighton here.
And this is the sample code
package com.hascode.tutorial;
import static com.hascode.tutorial.GraphUtil.registerShutdownHook;
import org.neo4j.graphalgo.GraphAlgoFactory;
import org.neo4j.graphalgo.PathFinder;
import org.neo4j.graphalgo.WeightedPath;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.index.Index;
import org.neo4j.kernel.EmbeddedGraphDatabase;
import org.neo4j.kernel.Traversal;
public class RailroadExample {
private static String DB_PATH = "/tmp/neo4j";
public static void main(final String[] args) {
GraphDatabaseService graphDb = new EmbeddedGraphDatabase(DB_PATH);
registerShutdownHook(graphDb);
Index<Node> nodeIndex = graphDb.index().forNodes("nodes");
Transaction tx = graphDb.beginTx();
try {
Node londonNode = graphDb.createNode();
londonNode.setProperty("name", "London");
nodeIndex.add(londonNode, "name", "London");
Node brightonNode = graphDb.createNode();
brightonNode.setProperty("name", "Brighton");
nodeIndex.add(brightonNode, "name", "Brighton");
Node portsmouthNode = graphDb.createNode();
portsmouthNode.setProperty("name", "Portsmouth");
nodeIndex.add(portsmouthNode, "name", "Portsmouth");
Node bristolNode = graphDb.createNode();
bristolNode.setProperty("name", "Bristol");
nodeIndex.add(bristolNode, "name", "Bristol");
Node oxfordNode = graphDb.createNode();
oxfordNode.setProperty("name", "Oxford");
nodeIndex.add(oxfordNode, "name", "Oxford");
Node gloucesterNode = graphDb.createNode();
gloucesterNode.setProperty("name", "Gloucester");
nodeIndex.add(gloucesterNode, "name", "Gloucester");
Node northamptonNode = graphDb.createNode();
northamptonNode.setProperty("name", "Northampton");
nodeIndex.add(northamptonNode, "name", "Northampton");
Node southamptonNode = graphDb.createNode();
southamptonNode.setProperty("name", "Southampton");
nodeIndex.add(southamptonNode, "name", "Southampton");
// london -> brighton ~ 52mi
Relationship r1 = londonNode.createRelationshipTo(brightonNode,
RelTypes.LEADS_TO);
r1.setProperty("distance", 52);
// brighton -> portsmouth ~ 49mi
Relationship r2 = brightonNode.createRelationshipTo(portsmouthNode,
RelTypes.LEADS_TO);
r2.setProperty("distance", 49);
// portsmouth -> southampton ~ 20mi
Relationship r3 = portsmouthNode.createRelationshipTo(
southamptonNode, RelTypes.LEADS_TO);
r3.setProperty("distance", 20);
// london -> oxford ~95mi
Relationship r4 = londonNode.createRelationshipTo(oxfordNode,
RelTypes.LEADS_TO);
r4.setProperty("distance", 95);
// oxford -> southampton ~66mi
Relationship r5 = oxfordNode.createRelationshipTo(southamptonNode,
RelTypes.LEADS_TO);
r5.setProperty("distance", 66);
// oxford -> northampton ~45mi
Relationship r6 = oxfordNode.createRelationshipTo(northamptonNode,
RelTypes.LEADS_TO);
r6.setProperty("distance", 45);
// northampton -> bristol ~114mi
Relationship r7 = northamptonNode.createRelationshipTo(bristolNode,
RelTypes.LEADS_TO);
r7.setProperty("distance", 114);
// southampton -> bristol ~77mi
Relationship r8 = southamptonNode.createRelationshipTo(bristolNode,
RelTypes.LEADS_TO);
r8.setProperty("distance", 77);
// northampton -> gloucester ~106mi
Relationship r9 = northamptonNode.createRelationshipTo(
gloucesterNode, RelTypes.LEADS_TO);
r9.setProperty("distance", 106);
// gloucester -> bristol ~35mi
Relationship r10 = gloucesterNode.createRelationshipTo(bristolNode,
RelTypes.LEADS_TO);
r10.setProperty("distance", 35);
tx.success();
System.out
.println("searching for the shortest route from London to Bristol..");
PathFinder<WeightedPath> finder = GraphAlgoFactory.dijkstra(
Traversal.expanderForTypes(RelTypes.LEADS_TO,
Direction.BOTH), "distance");
WeightedPath path = finder.findSinglePath(londonNode, bristolNode);
System.out.println("London - Bristol with a distance of: "
+ path.weight() + " and via: ");
for (Node n : path.nodes()) {
System.out.print(" " + n.getProperty("name"));
}
System.out
.println("\nsearching for the shortest route from Northampton to Brighton..");
path = finder.findSinglePath(northamptonNode, brightonNode);
System.out.println("Northampton - Brighton with a distance of: "
+ path.weight() + " and via: ");
for (Node n : path.nodes()) {
System.out.print(" " + n.getProperty("name"));
}
} finally {
tx.finish();
}
graphDb.shutdown();
}
}
Running the code gives us the following output
searching for the shortest route from London to Bristol..
London - Bristol with a distance of: 198.0 and via:
London Brighton Portsmouth Southampton Bristol
searching for the shortest route from Northampton to Brighton..
Northampton - Brighton with a distance of: 180.0 and via:
Northampton Oxford Southampton Portsmouth Brighton
Example: Mapping Users to Roles to Permissions
As another example we’re going to implement a system that assigns users to roles and permissions to roles.
There are three different roles: guest, user and admin and three permissions: read, write and administer.
We’re now building a graph to map the different permissions but you might ask where there is the advantage of using a graph for this?
We could make the scenario a bit more difficult and say that there are combined roles .. e.g. role root is a part of the role admin and the permissions read, write and administer are all parts of a permission named godmode – using a graph there we only need to descend the graph until we’ve got all permissions for the user mapped by the different possible paths in the graph.
We’re using a fresh enum to define our relation types here UserRelTypes
package com.hascode.tutorial;
import org.neo4j.graphdb.RelationshipType;
public enum UserRelTypes implements RelationshipType {
MEMBER_OF, HAS_ROLE, HAS_PERMISSION
}
Now to create the node structure
package com.hascode.tutorial;
import static com.hascode.tutorial.GraphUtil.cleanUp;
import static com.hascode.tutorial.GraphUtil.registerShutdownHook;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.ReturnableEvaluator;
import org.neo4j.graphdb.StopEvaluator;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.Traverser;
import org.neo4j.graphdb.index.Index;
import org.neo4j.kernel.EmbeddedGraphDatabase;
public class UserRolePermissionExample {
private static String DB_PATH = "/tmp/neo4j";
public static void main(final String[] args) {
GraphDatabaseService graphDb = new EmbeddedGraphDatabase(DB_PATH);
registerShutdownHook(graphDb);
Index<Node> nodeIndex = graphDb.index().forNodes("nodes");
Transaction tx = graphDb.beginTx();
try {
// cleanup first for this tutorial
cleanUp(graphDb, nodeIndex);
// 1. define possible permissions
// permission:read
Node permissionRead = graphDb.createNode();
permissionRead.setProperty("permission", "read");
nodeIndex.add(permissionRead, "permission", "read");
// permission:write
Node permissionWrite = graphDb.createNode();
permissionWrite.setProperty("permission", "write");
nodeIndex.add(permissionWrite, "permission", "write");
// permission:administer
Node permissionAdminister = graphDb.createNode();
permissionAdminister.setProperty("permission", "administer");
nodeIndex.add(permissionAdminister, "permission", "administer");
// 2. define possible roles
// role:guest
Node roleGuest = graphDb.createNode();
roleGuest.setProperty("role", "guest");
nodeIndex.add(roleGuest, "role", "guest");
// role:user
Node roleUser = graphDb.createNode();
roleUser.setProperty("role", "user");
nodeIndex.add(roleUser, "role", "user");
Node roleAdmin = graphDb.createNode();
roleAdmin.setProperty("role", "admin");
nodeIndex.add(roleAdmin, "role", "admin");
// 3. assign permissions to roles
// guests are only allowed to read
roleGuest.createRelationshipTo(permissionRead,
UserRelTypes.HAS_PERMISSION);
// users my read and write
roleUser.createRelationshipTo(permissionRead,
UserRelTypes.HAS_PERMISSION);
roleUser.createRelationshipTo(permissionWrite,
UserRelTypes.HAS_PERMISSION);
// administrators may read, write and administer
roleAdmin.createRelationshipTo(permissionRead,
UserRelTypes.HAS_PERMISSION);
roleAdmin.createRelationshipTo(permissionWrite,
UserRelTypes.HAS_PERMISSION);
roleAdmin.createRelationshipTo(permissionAdminister,
UserRelTypes.HAS_PERMISSION);
// 4. finally create some users and assign roles
// egon is a guest
Node egon = graphDb.createNode();
egon.setProperty("name", "Egon");
nodeIndex.add(egon, "name", "Egon");
egon.createRelationshipTo(roleGuest, UserRelTypes.HAS_ROLE);
// winston is a user
Node winston = graphDb.createNode();
winston.setProperty("name", "Winston");
nodeIndex.add(winston, "name", "Winston");
winston.createRelationshipTo(roleUser, UserRelTypes.HAS_ROLE);
// slimer is - of course - an admin
Node slimer = graphDb.createNode();
slimer.setProperty("name", "Slimer");
nodeIndex.add(slimer, "name", "Slimer");
slimer.createRelationshipTo(roleAdmin, UserRelTypes.HAS_ROLE);
tx.success();
System.out
.println("Looking up permissions for user with name=Winston");
Node userWinston = nodeIndex.get("name", "Winston").getSingle();
Traverser permissionTraverser = getPermissionTraverser(userWinston);
printPermissions(userWinston, permissionTraverser);
System.out
.println("Looking up permissions for user with name=Slimer");
Node userSlimer = nodeIndex.get("name", "Slimer").getSingle();
permissionTraverser = getPermissionTraverser(userSlimer);
printPermissions(userSlimer, permissionTraverser);
} finally {
tx.finish();
}
graphDb.shutdown();
}
private static void printPermissions(final Node userNode,
final Traverser permissionTraverser) {
final String userName = (String) userNode.getProperty("name");
for (Node node : permissionTraverser) {
if (node.hasProperty("role")) {
System.out.println(userName + " has the role: "
+ node.getProperty("role"));
}
if (node.hasProperty("permission")) {
System.out.println(userName + " has permission: "
+ node.getProperty("permission") + " at depth: "
+ (permissionTraverser.currentPosition().depth() - 1));
}
}
}
private static Traverser getPermissionTraverser(final Node userNode) {
return userNode.traverse(Traverser.Order.DEPTH_FIRST,
StopEvaluator.END_OF_GRAPH,
ReturnableEvaluator.ALL_BUT_START_NODE, UserRelTypes.HAS_ROLE,
Direction.OUTGOING, UserRelTypes.HAS_PERMISSION,
Direction.OUTGOING);
}
}
And this is the output
Looking up permissions for user with name=Winston
Winston has the role: user
Winston has permission: read at depth: 1
Winston has permission: write at depth: 1
Looking up permissions for user with name=Slimer
Slimer has the role: admin
Slimer has permission: read at depth: 1
Slimer has permission: write at depth: 1
Slimer has permission: administer at depth: 1
Tutorial Sources
I have put the source from this tutorial on my GitHub repository – download it there or check it out using Git:
git clone https://github.com/hascode/neo4j-tutorial.git
yED Graph Editor
Troubleshooting
-
“Exception in thread “main” java.util.NoSuchElementException: More than one element in org.neo4j.index.impl.lucene.LuceneIndex$1@12789d2. First element is ‘Node[2]‘ and the second element is ‘Node[4]‘
at org.neo4j.helpers.collection.IteratorUtil.singleOrNull(IteratorUtil.java:116)
at org.neo4j.index.impl.lucene.IdToEntityIterator.getSingle(IdToEntityIterator.java:88)
at org.neo4j.index.impl.lucene.IdToEntityIterator.getSingle(IdToEntityIterator.java:32)
at com.hascode.tutorial.IndexSearchExample.main(IndexSearchExample.java:45)” – if you’re running the tutorial sources or you’re just experimenting – you eventually might need to reset your graph or your node index – the following method helps you therepublic void cleanUp(final GraphDatabaseService graphDb, final Index<Node> nodeIndex) { for (Node node : graphDb.getAllNodes()) { for (Relationship rel : node.getRelationships()) { rel.delete(); } nodeIndex.remove(node); node.delete(); }