Big data analytics with Neo4j and Java, Part 2

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

1 2 Page 2
Page 2 of 2

Listing 3. Neo4jClient.java


package com.geekcap.javaworld.neo4j;

import com.geekcap.javaworld.neo4j.model.Movie;
import com.geekcap.javaworld.neo4j.model.Person;
import org.neo4j.driver.v1.*;
import org.neo4j.driver.v1.types.Node;

import java.util.HashSet;
import java.util.Set;

import static org.neo4j.driver.v1.Values.parameters;

public class Neo4jClient {

    /**
     * Neo4j Driver, used to create a session that can execute Cypher queries
     */
    private Driver driver;

    /**
     * Create a new Neo4jClient. Initializes the Neo4j Driver.
     */
    public Neo4jClient() {
        // Create the Neo4j driver
        driver = GraphDatabase.driver( "bolt://localhost:7687", AuthTokens.basic("neo4j", "neo4j"));
    }

    /**
     * Create a new Person.
     * @param person    The Person to create
     */
    public void createPerson(Person person) {
        // Create a Neo4j session. Because the Session object is AutoCloseable, we can use a try-with-resources statement
        try (Session session = driver.session()) {

            // Execute our create Cypher query
            session.run("CREATE (person: Person {name: {name}, age: {age}})",
                    parameters("name", person.getName(), "age", person.getAge()));
        }
    }

    /**
     * Finds all Person objects in the Neo4j database.
     * @return  A set of all Person objects in the Neo4j database.
     */
    public Set<Person> findAllPeople() {
        // Create a set to hold our people
        Set<Person> people = new HashSet<>();

        // Create a Neo4j session
        try (Session session = driver.session()) {

            // Execute our query for all Person nodes
            StatementResult result = session.run("MATCH(person:Person) RETURN person");

            // Iterate over the response
            for (Record record: result.list()) {
                // Load the Neo4j node from the record by the name "person", from our RETURN statement above
                Node person = record.get("person").asNode();

                // Build a new person object and add it to our result set
                Person p = new Person();
                p.setName(person.get("name").asString());
                if (person.containsKey("age")) {
                    p.setAge(person.get("age").asInt());
                }
                people.add(p);
            }
        }

        // Return the set of people
        return people;
    }

    /**
     * Returns the friends of the requested person.
     *
     * @param person    The person for which to retrieve all friends
     * @return          A Set that contains all Person objects for which there is a FRIEND relationship from
     *                  the specified person
     */
    public Set<Person> findFriends(Person person) {
        // A Set to hold our response
        Set<Person> friends = new HashSet<>();

        // Create a session to Neo4j
        try (Session session = driver.session()) {
            // Execute our query
            StatementResult result = session.run("MATCH (person: Person {name: {name}})-[:FRIEND]-(friend: Person) RETURN friend",
                    parameters("name", person.getName()));

            // Iterate over our response
            for (Record record: result.list()) {

                // Create a Person
                Node node = record.get("friend").asNode();
                Person friend = new Person(node.get("name").asString());

                // Add the person to the friend set
                friends.add(friend);
            }
        }

        // Return the set of friends
        return friends;
    }

    /**
     * Find all movies (with rating) seen by the specified Person.
     *
     * @param person    The Person for which to find movies seen
     * @return          A Set of Movies (with ratings)
     */
    public Set<Movie> findMoviesSeenBy(Person person) {
        Set<Movie> movies = new HashSet<>();
        try (Session session = driver.session()) {
            // Execute our query
            StatementResult result = session.run("MATCH (person: Person {name: {name}})-[hasSeen:HAS_SEEN]-(movie:Movie) RETURN movie.title, hasSeen.rating",
                    parameters("name", person.getName()));

            // Iterate over our response
            for (Record record: result.list()) {

                Movie movie = new Movie(record.get("movie.title").asString());
                movie.setRating(record.get("hasSeen.rating").asInt());
                movies.add(movie);
            }
        }
        return movies;
    }

    /**
     * Helper method that prints a person set to the standard output.
     * @param people    The set of Person objects to print to the standard output
     */
    public static void printPersonSet(Set<Person> people) {
        for (Person person: people) {
            StringBuilder sb = new StringBuilder("Person: ");
            sb.append(person.getName());
            if (person.getAge()>0) {
                sb.append(" is " + person.getAge() + " years old");
            }
            System.out.println(sb);
        }
    }


    /**
     * Test methods
     */
    public static void main(String ... args) {
        Neo4jClient client = new Neo4jClient();
        client.createPerson(new Person("Duke", 22));

        Set<Person> people = client.findAllPeople();
        System.out.println("ALL PEOPLE");
        printPersonSet(people);

        Set<Person> friendsOfMichael = client.findFriends(new Person("Michael"));
        System.out.println("FRIENDS OF MICHAEL");
        printPersonSet(friendsOfMichael);

        Set<Movie> moviesSeenByMichael = client.findMoviesSeenBy(new Person("Michael"));
        System.out.println("MOVIES MICHAEL HAS SEEN:");
        for (Movie movie: moviesSeenByMichael) {
            System.out.println("Michael gave the movie " + movie.getTitle() + " a rating of " + movie.getRating());
        }
    }
}

Example app: The Neo4j client class

The Neo4jClient class creates a Neo4j Driver in its constructor. Its methods then use that Driver to create a Session object in order to execute Cypher queries. The createPerson() method executes a CREATE (person:Person {...}) Cypher query with named parameters for "name" and "age." The parameters() method binds those parameters to the specified Person's name and age properties.

The findAllPeople() method finds all Person objects in the database. Note that this method returns all people, so if you have a lot of people you might want to add a LIMIT to the response. Here's an example:

MATCH (person:Person) RETURN person LIMIT 25

In this case, we're returning full Person nodes, so we retrieve each node from the Record by requesting the "person" and converting it to a Node with the asNode() method.

The findFriends() method does the same thing, but it executes a different Cypher query:

MATCH (person: Person {name: {name}})-[:FRIEND]-(friend: Person) RETURN friend

We're requesting the person with a specified name, then following that person's FRIEND relationships to all Person nodes, giving each node the name "friend." So, when we retrieve the response from the Record, we ask for the "friend" and convert it to a Node.

Finally, the findMoviesSeenBy() method executes the following Cypher query:

MATCH (person: Person {name: {name}})-[hasSeen:HAS_SEEN]-(movie:Movie) RETURN movie.title, hasSeen.rating

This query starts from the named person and follows all HAS_SEEN relationships to Movie nodes. It then returns the movie title property as movie.title and the rating as hasSeen.rating.

In order to do this we had to specify a variable name, hasSeen, in our HAS_SEEN relationship. Because we asked for the movie title, which is a String, and the rating, which is an integer, we retrieved both individually from the Record::


record.get("movie.title").asString()
record.get("hasSeen.rating").asInt()

The main() method creates a new Neo4jClient, creates a Person named "Duke," who is 22 years old (hint: just like Java). It finds both Michael's friends and the movies that he's seen.

Listing 4 shows the Maven pom.xml file, which we use to build and run our app.

Listing 4. Maven POM for the Neo4jClient app


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.geekcap.javaworld</groupId>
    <artifactId>neo4j-example</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>neo4j-example</name>
    <url>http://maven.apache.org</url>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- Import Neo4j -->
        <dependency>
            <groupId>org.neo4j.driver</groupId>
            <artifactId>neo4j-java-driver</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>com.geekcap.javaworld.neo4j.Neo4jClient</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy</id>
                        <phase>install</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

This pom.xml file imports the neo4j-java-driver dependency and then defines three plugins:

  • maven-compiler-plugin sets the Java build version to 1.8.
  • maven-jar-plugin makes the resultant JAR file executable with the main class set to com.geekcap.javaworld.neo4j.Neo4jClient and includes all files in the lib directory in the JAR file's CLASSPATH.
  • maven-dependency-plugin copies all dependencies to the project build directory's lib folder.

Build and run your Neo4j client application

You can now build the Neo4j client application using the following command:

mvn clean install

You can run it from the target directory with the following command:

java -jar neo4j-example-1.0-SNAPSHOT.jar

You should see output similar to the following:


ALL PEOPLE
Person: Steven is 45 years old
Person: Jordyn
Person: Michael is 16 years old
Person: Katie
Person: Koby
Person: Duke is 22 years old
Person: Grant
Person: Rebecca is 7 years old
Person: Linda
Person: Charlie is 16 years old
FRIENDS OF MICHAEL
Person: Charlie
Person: Grant
Person: Koby
MOVIES MICHAEL HAS SEEN:
Michael gave movie Avengers a rating of 5

Be sure to navigate over to your Neo4j web interface and execute few queries! You should see that Duke was created and be able to validate the results.

Conclusion to Part 2

Neo4j is a graph database that manages highly related data. We started this exploration by reviewing the need for graph databases, especially for querying more than three degrees of separation in relationships. After getting set up with Neo4j in a development environment, we spent most of Part 1 getting to know Neo4j's Cypher Query Language. We modeled a web of family relationships and queried those relationships using Cypher. Our focus in that article was learning how to think graphically. That is the power of Neo4j, and also the most challenging feature for most developers to master.

In Part 2, you've learned how to write a Java application that connects to Neo4j and executes Cypher queries. We took the simplest (manual) approach to integrating Java with Neo4j. Once you've got the basics down, you may want to explore more advanced ways of integrating Java with Neo4j--such as by using Neo4j's Object-Graph-Mapping (OGM) library, Neo4j-OGM, and Spring Data.

1 2 Page 2
Page 2 of 2