Write a Java application that connects to Neo4j and executes Cypher queries Credit: Rafiq Phillips 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 Download the code 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; } } 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. Software DevelopmentOpen SourceData ManagementDevelopment Tools