顯示具有 Graph Database 標籤的文章。 顯示所有文章
顯示具有 Graph Database 標籤的文章。 顯示所有文章

2022年1月25日 星期二

使用 Java 讀取 Neo4j 的查詢結果

Neo4j 是一套原生實現 (底層設計就是為了圖資料庫設計,而不是用例如一般關聯式資料庫去模擬) 圖資料庫(Graph Database) 的工具

Neo4j Community Edition 版本可以到 Neo4j 官網的下載中心 免費下載使用,
除了有啟動 Neo4j server 的功能外還提供了以網頁存取的方便介面 。

下載 Neo4j Community Edition 後,把下載下來的 zip 檔解壓縮,
進入到資料夾裡的 bin 資料夾,用命令列模式 (command line)
打上

neo4j console

指令後,可以用瀏覽器到 http://localhost:7474/browser/ 看到 UI 介面,提供各種功能,例如執行語法及可視化結果,預設登入 Database 的帳號密碼會都是 "neo4j" ,可以自行修改。

這邊紀錄下使用 Java 去讀取 Neo4j 查詢結果的方法,首先來看下需要的依賴 Maven Dependency:

<!-- https://mvnrepository.com/artifact/org.neo4j.driver/neo4j-java-driver -->
	<dependency>
	    <groupId>org.neo4j.driver</groupId>
	    <artifactId>neo4j-java-driver</artifactId>
	    <version>4.4.2</version>
	</dependency>
	
	<!-- https://mvnrepository.com/artifact/org.neo4j/neo4j-jdbc-driver -->
	<dependency>
	    <groupId>org.neo4j</groupId>
	    <artifactId>neo4j-jdbc-driver</artifactId>
	    <version>4.0.4</version>
	    <scope>runtime</scope>
	</dependency>
    
   <!-- https://mvnrepository.com/artifact/org.neo4j/neo4j -->
	<!-- neo4j embedded version -->
	<dependency>
	    <groupId>org.neo4j</groupId>
	    <artifactId>neo4j</artifactId>
	    <version>4.4.2</version>
	</dependency>
    
以上三個 neo4j 的 dependency 可以擇一使用,
可依你想要存取 neo4j 的方式來選擇,分述如下:
  1. org.neo4j.driver 的 neo4j-java-driver :
    使用 Driver 的方式來存取 neo4j ,有較接近 neo4j 原生結構的類別可操作使用,
    需要跟已開啟的 neo4j server 做連線,使用像例如
    neo4j://localhost:7687
    這樣的方式來操縱 neo4j 資料庫。
  2. org.neo4j 的 neo4j-jdbc-driver :
    使用 JDBC 的方式來存 neo4j ,
    需要跟已開啟的 neo4j server 做連線,使用像例如
    jdbc:neo4j:bolt://localhost:7687?user=xxx,password=xxx,scheme=basic
    的方式操縱 neo4j 資料庫。
    跟 neo4j-java-driver 比起來,neo4j-jdbc-driver 沒有接近 neo4j 原生結構的類別可操作使用,
    只能使用 jdbc 的 (Map) ResultSet.getObject() 方式等存取 
  3. org.neo4j 的 neo4j :
    使用 嵌入式(embedded) 的方式來存取本地端的 neo4j 資料庫檔案,
    可以直接處理本地端 neo4j 資料庫檔案 (通常為一個資料夾),
    不需開啟 neo4j server 做連線,適合用在無 server 的環境,
    有較接近 neo4j 原生結構的類別可操作使用,
    例如可直接對整個 neo4j-community-4.4.2 資料夾及指定 Database 名稱來做存取。
接下來我們先來建立一個簡單的 neo4j 資料庫內容,
內容為一個英雄人物曾當過哪些英雄的關係圖,像是這個樣子:
Bruce Wayne -[hasBeenHero] -> Batman
Dick Grayson -[hasBeenHero] -> Batman
Dick Grayson -[hasBeenHero] -> Nightwing
Dick Grayson -[hasBeenHero] -> Robin

建構資料的語法如下
//create "Persion" nodes
MERGE (bruceWayne:Person {name: 'Bruce Wayne'})
MERGE (dickGrayson:Person {name: 'Dick Grayson'})
//create "Hero" nodes
WITH bruceWayne,
	 dickGrayson, 
	 [
	 	{name: 'Batman'},
	 	{name: 'Nightwing'},
	 	{name: 'Robin'}
	 ] AS heros
//create and set relatoinship
FOREACH (hero in heros | 
    CREATE (h:Hero) SET h = hero
    CREATE (dickGrayson)-[:hasBeenHero]->(h)
)
WITH bruceWayne
MATCH (batman:Hero{name:'Batman'})
CREATE (bruceWayne)-[:hasBeenHero]->(batman)
用視覺化來看的話資料庫結果會如下圖:

接下來我們來看看要如何用 Java 把資料庫的 Node 和 Relationship 都查出來,
以下直接上 Java 程式碼,實現了三個 method ,分別對應了上述的三種存取 neo4j Database 的方式,分別是:
queryByDriver() 對應 org.neo4j.driver 的 neo4j-java-driver
queryByJdbc() 對應 org.neo4j 的 neo4j-jdbc-driver
queryByEmbeddedMode() 對應 org.neo4j 的 neo4j

 Neo4jTest.java :
package main;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.neo4j.dbms.api.DatabaseManagementService;
import org.neo4j.dbms.api.DatabaseManagementServiceBuilder;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.Record;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.types.Node;
import org.neo4j.driver.types.Relationship;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Transaction;
import org.neo4j.kernel.impl.core.NodeEntity;
import org.neo4j.kernel.impl.core.RelationshipEntity;

public class Neo4jTest {
	
	public static void main(String... args) throws Exception {
		String databaseName = "neo4j";
		String userName = "neo4j";
		String password = "neo4j";
		String neo4jServerUrl = "localhost:7687";
		Path neo4jDBHomeDirectoryPath = Paths.get(ClassLoader.getSystemResource("neo4j-community-4.4.2").toURI());
		//or if the database directory is located on other path:
		//Path neo4jDBHomeDirectoryPath = Paths.get("D:\\xxx\\yyy\\neo4j-community-4.4.2");
		
		queryByDriver("neo4j://" + neo4jServerUrl, userName, password);
		queryByJdbc(neo4jServerUrl, userName, password);
		queryByEmbeddedMode(neo4jDBHomeDirectoryPath, databaseName);
		
		System.out.println("Done");
	}
	
	public static void queryByDriver(String uri, String username, String password) {		
		
		try (Driver driver = GraphDatabase.driver(uri, AuthTokens.basic(username, password));
				Session session = driver.session();) {
			
			List<String> dataList = session.readTransaction(tx -> {
				List<String> resultList = new ArrayList<String>();
				
				Result result = tx.run("MATCH (person:Person)-[relationship:hasBeenHero]->(hero:Hero) "
									 + "RETURN * "
									 + "ORDER BY person.name, hero.name");
				
				while(result.hasNext()) {					
					Record record = result.next();
					Node person = record.get("person").asNode();
					Relationship relationship = record.get("relationship").asRelationship();
					Node hero = record.get("hero").asNode();
					
					String resultStr = person.get("name").asString() + " -" + relationship.type() + "-> " + hero.get("name").asString();
//					System.out.println(resultStr); // who -hasBeenHero-> whatHero
					resultList.add(resultStr);
				}
				
				return resultList;
			});
			
			for (String data : dataList) {
				System.out.println(data);
				/* output:
				   Bruce Wayne -hasBeenHero-> Batman
				   Dick Grayson -hasBeenHero-> Batman
				   Dick Grayson -hasBeenHero-> Nightwing
				   Dick Grayson -hasBeenHero-> Robin
				*/
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void queryByJdbc(String uri, String username, String password) {		
		try (Connection con = DriverManager.getConnection("jdbc:neo4j:bolt://" + uri + "?user=" + username + ",password=" + password +",scheme=basic");
				PreparedStatement pstmt = con.prepareStatement("MATCH (person:Person)-[relationship:hasBeenHero]->(hero:Hero) "
															 + "RETURN * "
															 + "ORDER BY person.name, hero.name");
				ResultSet rs = pstmt.executeQuery();
				){
			
			while(rs.next()) {
				Map person = (Map) rs.getObject("person");				
				Map relationship = (Map) rs.getObject("relationship");
				Map hero = (Map) rs.getObject("hero");
				String resultStr = person.get("name") + " -" + relationship.get("_type") + "-> " + hero.get("name");
				System.out.println(resultStr);
				/* output:
				   Bruce Wayne -hasBeenHero-> Batman
				   Dick Grayson -hasBeenHero-> Batman
				   Dick Grayson -hasBeenHero-> Nightwing
				   Dick Grayson -hasBeenHero-> Robin
				*/				
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	// Don't need to start server manually, it will start server by itself.
	// Don't need username and password.
	public static void queryByEmbeddedMode(Path neo4jDBHomeDirectoryPath, String databaseName) {
		DatabaseManagementService databaseManagementService = new DatabaseManagementServiceBuilder(neo4jDBHomeDirectoryPath).build();
		GraphDatabaseService graphDatabaseService = databaseManagementService.database(databaseName);
		
		// Registers a shutdown hook for the Neo4j instance so that it
	    // shuts down nicely when the VM exits (even if you "Ctrl-C" the
	    // running application).
//	    Runtime.getRuntime().addShutdownHook( new Thread()
//	    {
//	        @Override
//	        public void run()
//	        {
//	        	databaseManagementService.shutdown();
//	        }
//	    } );
	    
	    try(Transaction tx = graphDatabaseService.beginTx();){
	    	org.neo4j.graphdb.Result result = tx.execute("MATCH (person:Person)-[relationship:hasBeenHero]->(hero:Hero) "
	    											   + "RETURN * "
	    											   + "ORDER BY person.name, hero.name");
			
			while(result.hasNext()) {				
				Map<String,Object> record = result.next();
				NodeEntity person = (NodeEntity) record.get("person");
				RelationshipEntity relationship = (RelationshipEntity) record.get("relationship");
				NodeEntity hero = (NodeEntity) record.get("hero");
				String resultStr = person.getProperty("name") + " -" + relationship.getType().name() + "-> " + hero.getProperty("name");
				System.out.println(resultStr);
				/* output:
				   Bruce Wayne -hasBeenHero-> Batman
				   Dick Grayson -hasBeenHero-> Batman
				   Dick Grayson -hasBeenHero-> Nightwing
				   Dick Grayson -hasBeenHero-> Robin
				*/
			}
	    }catch(Exception e) {
	    	e.printStackTrace();
	    }
	    
		databaseManagementService.shutdown();
	}
}
可以注意到的是,只有 queryByDriver() 和 queryByJdbc() 需要提供帳號及密碼,並且需要連接一個已經啟動的 neo4j Database server。
而 queryByEmbeddedMode() 不需帳號、密碼,也不需要一個已經啟動的 neo4j Database server,
它會直接對本地端的 neo4j Database folder 做存取,因為它會自己啟動 DB server ,
所以不要用例如上述的 neo4j console 指令啟動 server,不然可能會出現
Exception in thread "main" java.lang.RuntimeException: Error starting Neo4j database server at D:\xxx\yyy\neo4j-community-4.4.2\data\databases
的錯誤訊息。

原碼下載分享: