Groovy から Neo4j を使ってみた - スタンドアロン実行とRMIリモート接続によるNodeの追加と探索

グラフDBの Neo4j を Groovy から使ってみた。
具体的には、スタンドアロン実行と RMI を使ったリモート接続の各々で Node の追加と探索を試してみた。

使用した環境は以下の通り。

  • Groovy 1.7.4
  • Neo4j 1.1

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20100802/neo4j/

事前準備

まず、Groovy で Neo4j の JAR ファイルをロードできるようにしておく必要がある。
今回は、Groovy の conf/groovy-starter.conf ファイルに設定されている !{user.home}/.groovy/lib の場所に Neo4j の JAR ファイルを配置する事にした。

スタンドアロン実行

以下のような Node 構成をDB上に作成する。

EmbeddedGraphDatabase にデータ格納先ディレクトリを指定し、GraphDatabaseService の実装オブジェクトを取得する。(初回実行時には自動的にディレクトリが作成される)beginTx でトランザクションを開始し、GraphDatabaseService の createNode で Node の作成、Node の createRelationshipTo で Node 間の Relationship の設定を行う。

Relationship を定義するために、RelationshipType を実装した enum を使う方法が用意されているが、今回は DynamicRelationshipType で動的に定義する事にした。

Node追加 local_add_node.groovy
import org.neo4j.graphdb.DynamicRelationshipType
import org.neo4j.kernel.EmbeddedGraphDatabase

def db = new EmbeddedGraphDatabase("local-sample")
def tx = db.beginTx()

try {
    def know = DynamicRelationshipType.withName("knows")

    def n1 = db.createNode()
    n1.setProperty("name", "tester1")

    println("tester1 id = ${n1.id}")

    n11 = db.createNode()
    n11.setProperty("name", "tester1-1")
    n1.createRelationshipTo(n11, know)

    n111 = db.createNode()
    n111.setProperty("name", "tester1-1-1")
    n11.createRelationshipTo(n111, know)

    n1111 = db.createNode()
    n1111.setProperty("name", "tester1-1-1-1")
    n111.createRelationshipTo(n1111, know)

    n1112 = db.createNode()
    n1112.setProperty("name", "tester1-1-1-2")
    n111.createRelationshipTo(n1112, know)

    n12 = db.createNode()
    n12.setProperty("a", "tester1-2")
    n1.createRelationshipTo(n12, know)

    tx.success()
} catch (ex) {
    ex.printStackTrace()
} finally {
    tx.finish()
}

println("shutdown db ...")
db.shutdown()

getNodeById メソッドで指定 ID の Node を取得し、Node の traverse メソッドで Relationship で繋がっている Node の探索を実施する。
traverse の際は StopEvaluator インターフェースで探索を止めるタイミングを指定し、ReturnableEvaluator で結果として返すか否かを指定する。

Node探索 local_find_node.groovy
import org.neo4j.kernel.EmbeddedGraphDatabase
import org.neo4j.graphdb.*

def id = (args.length > 0)? args[0].toInteger(): 1

def db = new EmbeddedGraphDatabase("local-sample")

//Node の name プロパティの値を出力
def printName = {n -> 
    if (n.hasProperty("name")) {
        println n.id + " - " + n.getProperty("name")
    }
    else {
        println n.id + " - ** no name **"
    }
}

//指定 id の Node 取得
def n = db.getNodeById(id)

//Node の名前出力
printName(n)

//Node の全Relationship取得
n.relationships.each {
    //End Node の名前出力
    printName(it.endNode)
}

println "--------------------"

def know = DynamicRelationshipType.withName("knows")

//2層目で探索を停止
def s = {pos -> pos.depth() == 2} as StopEvaluator
//name プロパティを持つNodeのみ返す。
def r = {pos -> pos.currentNode().hasProperty("name")} as ReturnableEvaluator

//Relationship=know で繋がっている Node を 2層目まで探索
//(Relationship の向きはどちらでもよい)
def t = n.traverse(Traverser.Order.BREADTH_FIRST, s, r, know, Direction.BOTH)

t.each {
    printName(it)
}

println "---------------"

//全 Node 探索
def all = n.traverse(Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, ReturnableEvaluator.ALL, know, Direction.BOTH)

all.each {
    printName(it)
}

println("shutdown db ...")
db.shutdown()
実行例
>groovy local_add_node.groovy
tester1 id = 1
shutdown db ...

>groovy local_find_node.groovy
1 - Tester1
6 - ** no name **
2 - tester1-1
--------------------
1 - Tester1
2 - tester1-1
3 - tester1-1-1
---------------
1 - Tester1
6 - ** no name **
2 - tester1-1
3 - tester1-1-1
5 - tester1-1-1-2
4 - tester1-1-1-1
shutdown db ...

RMIによるリモート接続

Neo4j には RMI を使ったリモートDB接続を実施するための API が用意されている。
リモート接続を行うための基本的な手順は以下の通り。

  1. rmiregistry 起動
  2. RmiTransport.register で EmbeddedGraphDatabase をリモート公開
  3. RemoteGraphDatabase を使ってリモート接続

まず、neo4j-remote-graphdb-0.7.jar をクラスパスに設定して rmiregisty を起動しておく。

rmiregistry 起動
>rmiregistry -J-Djava.class.path=c:\neo4j-apoc-1.1\lib\neo4j-remote-graphdb-0.7.jar

次に、RMI で使用できるように EmbeddedGraphDatabase を RmiTransport.register で rmiregistry に登録する。
なお、今回は同一ホスト内で別プロセスによる接続を行うため、URI には localhost を使っている。

リモート公開 remote_server.groovy
import org.neo4j.kernel.EmbeddedGraphDatabase
import org.neo4j.remote.transports.LocalGraphDatabase
import org.neo4j.remote.transports.RmiTransport

def db = new EmbeddedGraphDatabase("remote-sample")

//RMI で使えるように rmiregistry に登録
//"rmi://localhost/remote-test" のURIで接続できるようになる
RmiTransport.register(new LocalGraphDatabase(db), "rmi://localhost/remote-test")

addShutdownHook {
    println("shutdown db ...")
    db.shutdown()
}

println "start server ... "
println "press key to stop"

System.in.read()
System.exit(0)
>groovy remote_server.groovy
start server ...
press key to stop

リモート接続時の Node の追加処理は、EmbeddedGraphDatabase の代わりに RemoteGraphDatabase を使う以外はスタンドアロン版(local_add_node.groovy)と同じ処理内容になる。

リモートNode追加 remote_add_node.groovy
import org.neo4j.remote.RemoteGraphDatabase
import org.neo4j.graphdb.DynamicRelationshipType

//接続先URIを指定
def db = new RemoteGraphDatabase("rmi://localhost/remote-test")
・・・

Node探索も RemoteGraphDatabase を使う点とトランザクション内で実施しなければならない点以外はスタンドアロン版(local_find_node.groovy)と同じ処理内容。

リモートNode探索 remote_find_node.groovy
import org.neo4j.remote.RemoteGraphDatabase
import org.neo4j.graphdb.*

def id = (args.length > 0)? args[0].toInteger(): 1

//接続先URIを指定
def db = new RemoteGraphDatabase("rmi://localhost/remote-test")

def tx = db.beginTx()
・・・
try {
    //id=1 の Node 取得
    def n = db.getNodeById(id)

    //id=1 の Node の名前出力
    printName(n)

    //Node の全Relationship取得
    n.relationships.each {
        //End Node の名前出力
        printName(it.endNode)
    }

    println "--------------------"

    def know = DynamicRelationshipType.withName("knows")

    //2層目で探索を停止
    def s = {pos -> pos.depth() == 2} as StopEvaluator
    //name プロパティを持つNodeのみ返す。
    def r = {pos -> pos.currentNode().hasProperty("name")} as ReturnableEvaluator

    //Relationship=know で繋がっている Node を 2層目まで探索
    //(Relationship の向きはどちらでもよい)
    def t = n.traverse(Traverser.Order.BREADTH_FIRST, s, r, know, Direction.BOTH)

    t.each {
        printName(it)
    }

    println "---------------"

    //全 Node 探索
    def all = n.traverse(Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, ReturnableEvaluator.ALL, know, Direction.BOTH)

    all.each {
        printName(it)
    }
} finally {
    tx.finish()
}
・・・

rmiregistry、remote_server.groovy が実行中の状態で以下を実行。

実行例
>groovy remote_add_node.groovy
tester1 id = 1
shutdown db ...

>groovy remote_find_node.groovy
1 - tester1
2 - tester1-1
6 - ** no name **
--------------------
1 - tester1
2 - tester1-1
3 - tester1-1-1
---------------
1 - tester1
2 - tester1-1
6 - ** no name **
3 - tester1-1-1
4 - tester1-1-1-1
5 - tester1-1-1-2
shutdown db ...