The first part of this article introduced Neo4j and its Cypher Query Language. If you've read Part 1, you've seen for yourself why Neo4j and other graph databases are especially popular for social graphing, or modeling relationships between users in a network. You also got Neo4j setup in your development environment, and you got an overview of the basic concepts of working with this data store--namely nodes and relationships.
We then used the Cypher Query Language to model a family in Neo4j, including personal attributes like age, gender, and the relationships between family members. We created some friends to broaden our social graph, then added key/value pairs to generate a list of movies that each user had seen. Finally, we queried our data, using graph analytics to search for a movie that one user had not seen but might enjoy.
Cypher Query Language is different from traditional data query languages like SQL. Rather than thinking about things like tables and foreign key relationships, Cypher forces you to think about nodes, natural relationships between nodes, and the various traversals that can be made between nodes across their relationships. Using Cypher, you create your own mental model about how real-world entities relate to one another. It takes some practice to get good at writing Cypher queries, but once you understand how they work, even very complicated queries will make sense.
Once you've modeled a social graph in Neo4j and written queries against that social graph using the Cypher Query Language, writing the Java code to execute queries to that graph is pretty easy. In this article you'll learn how to integrate Neo4j with a Java web client application, which you can use to query the social graph we created in Part 1.
Setup your Neo4j project
Our first step is to create a new Maven project:
mvn archetype:generate -DgroupId=com.geekcap.javaworld -DartifactId=neo4j-example -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
Open your pom.xml
file and add the Neo4j driver, which at the time of this writing is version 1.4.1:
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>1.4.1</version>
</dependency>
Create a Neo4j driver
Next, create a Neo4j Driver
, as follows:
Driver driver = GraphDatabase.driver( "bolt://localhost:7687", AuthTokens.basic("neo4j", "neo4j"));
The GraphDatabase
class has a static method called driver()
that accepts a URL to Neo4j and an AuthToken
. You can create a basic AuthToken
using the default username and password of "neo4j".
The Driver
facilitates communication with Neo4j. We execute queries by asking the Driver
to create a Session
object, as follows:
Session session = driver.session();
The Session interface
The org.neo4j.driver.v1.Session
interface executes transactions against Neo4j. In its simplest form, we can execute the run()
method that Session
inherits from org.neo4j.driver.v1.StatementRunner
. The Session
will then begin a transaction, run our statement, and commit that transaction.
The StatementRunner
interface defines a few variations of the run()
method. Here's the one we'll use:
StatementResult run(String statementTemplate, Map<String,Object> statementParameters)
Statement parameters
The statementTemplate
is a String
that contains our Cypher query, but also includes named parameters that we'll resolve using statementParameters
. For example, we might want to create a new Person
with a specified name and age:
session.run("CREATE (person: Person {name: {name}, age: {age}})",
parameters("name", person.getName(), "age", person.getAge()));
{name}
and {age}
are named parameters that can be resolved to values by passing in a Map
of String
s. Each String
contains the name of a property and must match what is in the String
template to values. The values are Object
s that the Session
will properly format in the Cypher query. The parameters
method is typically statically imported from the Values
object:
import static org.neo4j.driver.v1.Values.parameters
Managing transactions
After a Session
has completed, you are required to close it by invoking the close()
method. As a convenience, the Session
object implements the java.lang.AutoCloseable
interface, so starting in Java 7 you can execute it in a try-with-resources statement, such as:
try (Session session = driver.session()) {
session.run("CREATE (person: Person {name: {name}, age: {age}})",
parameters("name", person.getName(), "age", person.getAge()));
}
Finally, if you are executing multiple statements that you want to constrain to a single transaction, you are free to bypass the Session
's run()
method's automatic transaction management and explicitly manage a transaction yourself. For example:
try (Session session = driver.session()) {
try (Transaction tx = session.beginTransaction()) {
tx.run("CREATE (person: Person {name: {name}, age: {age}})",
parameters("name", person.getName(), "age", person.getAge()));
tx.success();
}
}
The call to Session.beginTransaction()
returns a Transaction
object that can be used to run Cypher statements. After executing the Cypher statements, you must call tx.success()
or the try-with-resources statement will roll back the transaction. The Transaction
interface is AutoCloseable
. If the transaction is marked as successful (by calling success()
) then the transaction is committed; otherwise the transaction is rolled back. You can explicitly fail a transaction by invoking the Transaction
's failure()
method.
Record objects
You may have observed that the run()
method in both the Session
and Transaction
classes returns a StatementResult
instance. The StatementResult
interface provides access to a list of Record
objects and each Record
object can have one or more Value
objects.
Similar to retrieving values from a JDBC ResultSet
, a Record
allows you to retrieve values either by index or by name. The Value
object that is returned can be converted to a Neo4j Node
by calling its asNode()
method or to a primitive, such as a String
or an integer, by invoking one of its other asXXX()
methods. Examples in the previous sections mostly returned nodes, but the last example returned a person's name as a String
. This is why the Value
object affords flexibility in its return types.
Example application in Java
Now we'll take what we've learned so far and put together an example application in Java. Building on our modeling and querying example in Part 1, this application creates Person
objects, finds all Person
objects, finds all friends of a Person
, and finds all movies that a Person
has seen.
Listing 1 and Listing 2 create Java classes defining a Person
and a Movie
. Listing 3 shows the source code for our test class: Neo4jClient
.
Listing 1. Person.java
package com.geekcap.javaworld.neo4j.model;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Listing 2. Movie.java
package com.geekcap.javaworld.neo4j.model;
public class Movie {
private String title;
private int rating;
public Movie() {
}
public Movie(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getRating() {
return rating;
}
public void setRating(int rating) {
this.rating = rating;
}
}