Big data analytics with Neo4j and Java, Part 2

Write a Java application that connects to Neo4j and executes Cypher queries

JavaWorld - JW - big data analytics - social graph
Rafiq Phillips (CC BY-SA 2.0)

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 Strings. Each String contains the name of a property and must match what is in the String template to values. The values are Objects 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.

download
Get the source code for the example application used in this article. Created by Steven Haines for JavaWorld.

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;
    }
}
1 2 Page 1
Page 1 of 2