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
的錯誤訊息。

原碼下載分享:

沒有留言 :

張貼留言