本文的 第一部分 介绍了Neo4j及其Cypher查询语言。如果您已经阅读了第1部分,那么您已经了解了为什么Neo4j和其他图形数据库特别受 社交图形 或网络中用户之间关系建模的影响。您还在开发环境中安装了Neo4j,并概述了使用此数据存储的基本概念 - 即节点和关系。
然后,我们使用Cypher查询语言对Neo4j中的一个家庭进行建模,包括年龄,性别和家庭成员之间的关系等个人属性。我们创建了一些朋友来扩大我们的社交图,然后添加键/值对来生成每个用户看过的电影列表。最后,我们查询了我们的数据,使用图形分析来搜索一个用户没有看到但可能喜欢的电影。
Cypher查询语言与SQL等传统数据查询语言不同。Cypher并没有考虑像表和外键关系这样的事情,而是强迫您考虑节点,节点之间的自然关系以及各个节点之间可以在各个关系之间进行的各种遍历。使用Cypher,您可以创建自己的心理模型,了解真实世界的实体如何相互关联。需要一些练习来擅长编写Cypher查询,但是一旦你理解了它们的工作方式,即使非常复杂的查询也是有意义的。
在使用Cypher查询语言对Neo4j中的社交图建模并使用该社交图编写查询后,编写Java代码以对该图执行查询非常简单。在本文中,您将学习如何将Neo4j与Java Web客户端应用程序集成,您可以使用它来查询我们在第1部分中创建的社交图。
我们的第一步是创建一个新的Maven项目:
mvn archetype:generate -DgroupId=com.geekcap.javaworld -DartifactId=neo4j-example -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
打开您的 pom.xml
文件并添加Neo4j驱动程序,在撰写本文时版本为1.4.1:
<dependency> <groupId>org.neo4j.driver</groupId> <artifactId>neo4j-java-driver</artifactId> <version>1.4.1</version> </dependency>
接下来,创建一个Neo4j Driver
,如下所示:
Driver driver = GraphDatabase.driver( "bolt://localhost:7687", AuthTokens.basic("neo4j", "neo4j"));
本 GraphDatabase
类有一个叫做静态方法 driver()
接受一个连接Neo4j的URL和 AuthToken
。您可以使用默认用户名和密码“neo4j” 创建基本 AuthToken
。
在 Driver
与Neo4j的促进通信。我们通过要求 Driver
创建 Session
对象来执行查询,如下所示:
Session session = driver.session();
该 org.neo4j.driver.v1.Session
接口对Neo4j执行事务。在最简单的形式中,我们可以执行继承自的 run()
方法。然后,将开始一个事务,运行我们的语句,并提交该事务。 Sessionorg.neo4j.driver.v1.StatementRunnerSession
该 StatementRunner
接口定义了的几个变型 run()
方法。这是我们将使用的那个:
StatementResult run(String statementTemplate, Map<String,Object> statementParameters)
该 statementTemplate
是一个包含我们的Cypher查询的 String
, statementParameters
包括我们将使用的命名参数。例如,我们可能想要创建具有指定名称和年龄的 Person
:
session.run("CREATE (person: Person {name: {name}, age: {age}})", parameters("name", person.getName(), "age", person.getAge()));
{name}和{age}
命名,可以通过传递解析为 String
值的 Map
。每个 String
都包含属性的名称,并且必须与模板中的值匹配。该 parameters
方法通常从 Values
对象静态导入:
import static org.neo4j.driver.v1.Values.parameters
一个 Session
已经完成后,你需要通过调用它的 close()
方法来关闭。为方便起见,该 Session
对象实现了 java.lang.AutoCloseable
接口,因此从Java 7开始,您可以在try-with-resources语句中执行它,例如:
try (Session session = driver.session()) { session.run("CREATE (person: Person {name: {name}, age: {age}})", parameters("name", person.getName(), "age", person.getAge())); }
最后,如果您正在执行的是要约束到一个单一事务的多条语句,你可以自由地绕过 Session
的 run()
方法的自动交易管理和明确自己管理的事务。例如:
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(); } }
该调用 Session.beginTransaction()
返回一个 Transaction
可用于运行Cypher语句的对象。执行Cypher语句后,必须调用 tx.success()
或try-with-resources语句将回滚事务。该 Transaction
实现 AutoCloseable
。如果事务被标记为成功(通过调用 success()
),则提交事务; 否则交易将被回滚。您可以通过调用 Transaction
的 failure()
方法明确失败交易。
您可能已经观察到 Session
和 Transaction
类中的 run()
方法都返回一个 StatementResult
实例。 StatementResult
接口可以访问 Record
的列表, Record
对象可以有一个或多个 Value
对象。
与从JDBC的 ResultSet
检索值类似, Record
允许您通过索引或按名称检索值。返回的 Value
对象可以通过调用 Node.asNode()
方法或原语(如 String
或整数),通过调用其他 asXXX()
方法之一转换为Neo4j 。前面几节中的示例主要返回节点,但最后一个示例将一个人的名称作为 String
返回。这就是为什么该 Value
对象在其返回类型中提供灵活性的原因。
现在我们将学习到目前为止所学到的知识,并将Java中的示例应用程序组合在一起。基于 第1部分中的建模和查询示例
,此应用程序创建 Person
对象,查找所有 Person
对象,查找a的所有朋友 Person
,并查找 Person
已看过的所有电影。
清单1和清单2创建了定义 Person
和a的Java类 Movie
。清单3显示了我们的测试类的源代码: Neo4jClient
。
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; } }
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; } }
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()); } } }
在 Neo4jClient
类在其构造中创建的Neo4j Driver
。然后它的方法使用 Driver
来创建一个 Session
对象以执行Cypher查询。 createPerson()
方法使用“name”和“age”的命名参数执行Cypher查询 CREATE (person:Person {...})
。 parameters()
方法将这些参数绑定到指定 Person
的名称和年龄属性。
findAllPeople()
方法查找 Person
数据库中的所有对象。请注意,此方法会返回
所有人
,因此如果您有很多人,则可能需要向响应中添加 LIMIT
。这是一个例子:
MATCH (person:Person) RETURN person LIMIT 25
在这种情况下,我们返回完整 Person
节点,因此我从 Record
中获取“person”并使用 Noded
的 asNode()
方法来转换。
findFriends()
方法执行相同的操作,但它执行不同的Cypher查询:
MATCH (person: Person {name: {name}})-[:FRIEND]-(friend: Person) RETURN friend
我们要求具有指定名称的人,然后查找该人 FRIEND
的关系,找到所有 Person
节点,为每个节点命名为“朋友”。因此,当我们从 Record
中检索响应时,我们要求“朋友”并将其转换为 Node
。
最后,该 findMoviesSeenBy()
方法执行以下Cypher查询:
MATCH (person: Person {name: {name}})-[hasSeen:HAS_SEEN]-(movie:Movie) RETURN movie.title, hasSeen.rating
此查询从指定人员开始,并遵循 HAS_SEEN
与 Movie
节点的所有关系。然后它返回电影标题属性 movie.title
和评级为 hasSeen.rating
。
为了做到这一点,我们必须在我们的 HAS_SEEN
关系中指定一个变量名 hasSeen
。因为我们要求电影标题和评级,我们从以下各项中单独检索 Record:
:
record.get("movie.title").asString() record.get("hasSeen.rating").asInt()
该 main()
方法创建一个新的 Neo4jClient
,创建一个22岁的名为“Duke”(提示:就像Java一样)的 Person
。它找到了迈克尔的朋友和他所见过的电影。
清单4显示了Maven pom.xml
文件,我们用它来构建和运行我们的应用程序。
<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>
此pom.xml文件导入 neo4j-java-driver
依赖项,然后定义三个插件:
com.geekcap.javaworld.neo4j.Neo4jClient
并包含 lib
JAR文件目录中的所有文件 CLASSPATH
。 lib
文件夹中。 您现在可以使用以下命令构建Neo4j客户端应用程序:
mvn clean install
您可以 target
使用以下命令从目录运行它:
java -jar neo4j-example-1.0-SNAPSHOT.jar
您应该看到类似于以下内容的输出:
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
务必导航到您的Neo4j Web界面并执行一些查询!您应该看到Duke已创建并能够验证结果。
Neo4j是一个管理高度相关数据的图形数据库。我们通过回顾图形数据库的需求开始了这种探索,尤其是在查询关系中三个以上的分离度时。在开发环境中使用Neo4j进行设置后,我们花了大部分时间来了解Neo4j的Cypher查询语言。我们建立了一个家庭关系网络,并使用Cypher查询了这些关系。我们在该文章中的重点是学习如何以 图形方式思考 。这是Neo4j的强大功能,也是大多数开发人员掌握的最具挑战性的功能。
在第2部分中,您学习了如何编写连接到Neo4j并执行Cypher查询的Java应用程序。我们采用最简单(手动)的方法将Java与Neo4j集成。一旦掌握了基础知识,您可能想要探索 将Java与Neo4j集成的 更高级方法 - 例如使用Neo4j的对象图形映射(OGM)库,Neo4j-OGM和Spring Data。