使用Neo4j和Java進行大資料分析 第2部分
本文的第一部分 介紹了Neo4j及其Cypher查詢語言。如果您已經閱讀了第1部分,那麼您已經瞭解了為什麼Neo4j和其他圖形資料庫特別受社交圖形 或網路中使用者之間關係建模的影響。您還在開發環境中安裝了Neo4j,並概述了使用此資料儲存的基本概念 - 即節點和關係。
然後,我們使用Cypher查詢語言對Neo4j中的一個家庭進行建模,包括年齡,性別和家庭成員之間的關係等個人屬性。我們建立了一些朋友來擴大我們的社交圖,然後新增鍵/值對來生成每個使用者看過的電影列表。最後,我們查詢了我們的資料,使用圖形分析來搜尋一個使用者沒有看到但可能喜歡的電影。
Cypher查詢語言與SQL等傳統資料查詢語言不同。Cypher並沒有考慮像表和外來鍵關係這樣的事情,而是強迫您考慮節點,節點之間的自然關係以及各個節點之間可以在各個關係之間進行的各種遍歷。使用Cypher,您可以建立自己的心理模型,瞭解真實世界的實體如何相互關聯。需要一些練習來擅長編寫Cypher查詢,但是一旦你理解了它們的工作方式,即使非常複雜的查詢也是有意義的。
在使用Cypher查詢語言對Neo4j中的社交圖建模並使用該社交圖編寫查詢後,編寫Java程式碼以對該圖執行查詢非常簡單。在本文中,您將學習如何將Neo4j與Java Web客戶端應用程式整合,您可以使用它來查詢我們在第1部分中建立的社交圖。
設定您的Neo4j專案
我們的第一步是建立一個新的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驅動程式
接下來,建立一個Neo4jDriver
,如下所示:
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();
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中的示例應用程式
現在我們將學習到目前為止所學到的知識,並將Java中的示例應用程式組合在一起。基於第1部分中的建模和查詢示例
,此應用程式建立Person
物件,查詢所有Person
物件,查詢a的所有朋友Person
,並查詢Person
已看過的所有電影。
清單1和清單2建立了定義Person
和a的Java類Movie
。清單3顯示了我們的測試類的原始碼:Neo4jClient
。
清單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; } }
清單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; } }
清單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 personThe 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. * @returnA 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 personThe person for which to retrieve all friends * @returnA 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 personThe Person for which to find movies seen * @returnA 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 peopleThe 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()); } } }
示例app:Neo4j客戶端類
在Neo4jClient
類在其構造中建立的Neo4jDriver
。然後它的方法使用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顯示了Mavenpom.xml
檔案,我們用它來構建和執行我們的應用程式。
清單4. Neo4jClient應用程式的Maven POM
<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
依賴項,然後定義三個外掛:
- maven-compiler-plugin 將Java構建版本設定為1.8。
-
maven-jar-plugin
使得主類設定為可執行的JAR檔案,
com.geekcap.javaworld.neo4j.Neo4jClient
幷包含lib
JAR檔案目錄中的所有檔案CLASSPATH
。 -
maven-dependency-plugin將
所有依賴項複製到專案構建目錄的
lib
資料夾中。
構建並執行您的Neo4j客戶端應用程式
您現在可以使用以下命令構建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已建立並能夠驗證結果。
第2部分的結論
Neo4j是一個管理高度相關資料的圖形資料庫。我們通過回顧圖形資料庫的需求開始了這種探索,尤其是在查詢關係中三個以上的分離度時。在開發環境中使用Neo4j進行設定後,我們花了大部分時間來了解Neo4j的Cypher查詢語言。我們建立了一個家庭關係網路,並使用Cypher查詢了這些關係。我們在該文章中的重點是學習如何以圖形方式思考 。這是Neo4j的強大功能,也是大多數開發人員掌握的最具挑戰性的功能。
在第2部分中,您學習瞭如何編寫連線到Neo4j並執行Cypher查詢的Java應用程式。我們採用最簡單(手動)的方法將Java與Neo4j整合。一旦掌握了基礎知識,您可能想要探索將Java與Neo4j整合的 更高階方法 - 例如使用Neo4j的物件圖形對映(OGM)庫,Neo4j-OGM和Spring Data。