Java で Neo4j Server を使う

Neo4j Wiki から

目次

[edit] Neo4j Server REST API

Neo4j server は REST API と webadmin アプリケーションを 1 つにまとめ、Neo4j を使ったソリューションのインストールと実行を容易にしたものです。REST API はもともと 0.8 リリースから移植されたもので、Neo4j Server とともに進化を遂げています。こうした背景事情があるため、既存のコードもそのままで動作します。

Java での REST API を介したグラフの作成

REST API が利用するのは HTTP と JSON なので、多くの言語とプラットフォームから REST API を使うことができます。ただし、再利用可能ないくつかのパターンをあらかじめ見ておくと便利です。この短いガイドでは、REST API を介して簡単なグラフを作成して操作する方法や、作成したグラフに対して問い合わせを行う方法を示します。

以下で示すサンプルでは、Jersey クライアントコンポーネントを使用しています。Jersey クライアントコンポーネントは、Maven 経由で簡単にダウンロードできます。

以下のサンプルのフルソースコードは Neo4j Subversion のリポジトリでブラウズできます。

[edit] サーバーの起動

サーバーで操作を実行できるようにするには、まず手順に従ってサーバーを起動する必要があります。サーバーを起動したら、サーバーのエントリポイント URI に対して HTTP GET を実行することで、サーバーが動作しているかどうか確認できます。

WebResource resource = Client.create().resource(SERVER_ROOT_URI);
ClientResponse response = resource.get(ClientResponse.class);

System.out.println(String.format("GET on [%s], status code [%d]", SERVER_ROOT_URI, response.getStatus()));

レスポンスのステータスが 200 OK なら、サーバーが適切に動作しているので、操作を続行できることがわかります。上のコードでサーバーへの接続に失敗する場合は、もう 1 度「Neo4j Server スタートガイド」を読み直してください。

200 OK 以外のレスポンス (特に 4xx や 5xx のレスポンス) が返された場合は、構成をチェックするとともに、data/log ディレクトリにある各種のログファイルを調べてください。

[edit] ノードの作成

REST API では、POST を使ってノードを作成します。Jersey クライアントを使えば、簡単に POST を Java でカプセル化できます。

WebResource resource = Client.create().resource(nodeEntryPointUri); // http://localhost:7474/db/data/node
ClientResponse response = resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).entity("{}"/*Empty Node*/).post(ClientResponse.class); // ノードのエントリポイント URI に対する POST {}
 
System.out.println(String.format("POST to [%s], status code [%d], location header [%s]", nodeEntryPointUri, response.getStatus(), response.getLocation().toString()));
 
return response.getLocation();

呼び出しが成功すると、背後では JSON ペイロードを含む HTTP リクエストがサーバーに送信されます。サーバーはデータベースに新しいノードを作成し、201 Created レスポンスと、新しく作成されたノードの URI を含む Location ヘッダを返します。

サンプルでは、実際には呼び出しを 2 回行って、データベースにノードを 2 つ作成しています。

[edit] プロパティの追加

データベースにノードを作成したら、これらのノードを使ってデータを格納することができます。ここでは、音楽に関する情報をデータベースに格納してみましょう。まず、ノードを作成してプロパティを追加するコードから見ていきましょう。このコードでは、"Joe Strummer"、それに "The Clash" という名前の band を表すノードを追加しています。

URI firstNode = createNode();
addProperty(firstNode, "name", "Joe Strummer");
URI secondNode = createNode();
addProperty(firstNode, "band", "The Clash");

addProperty メソッド内部では、ノードのプロパティを表すリソースを決定し、このプロパティの名前を指定しています。次に、このプロパティの値をサーバーに PUT します。

String propertyUri = nodeUri.toString() + "/properties/" + propertyName;
 
WebResource resource = Client.create().resource(propertyUri); // http://localhost:7474/db/data/node/{node_id}/properties/{property_name}
ClientResponse response = resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).entity(toJsonStringLiteral(propertyValue)).put(ClientResponse.class);
 
System.out.println(String.format("PUT to [%s], status code [%d]", propertyUri, response.getStatus()));

また、次のユーティリティ関数も使っています。


 private static String toJsonStringLiteral(String str) {
        return "\"" + str + "\"";
    }

処理が適切に行われれば、204 No Content レスポンスが返されます。これは、サーバーはリクエストを処理したけれども、プロパティの値のエコーバックはなかったという意味です。

[edit] リレーションの追加

Joe Strummer と The Clash を表すノードを作成したので、これらのノードの間にリレーションを作成してみましょう。REST API では、リレーションのスタートノードに対して、リレーションの表現を POST することで、リレーションを作成することができます。同様に Java では、Joe Strummer を表すノードの URI に対して何らかの JSON データを POST することで、Joe Strummer を表すノードと The Clash を表すノードとの間にリレーションを作成することができます。

URI relationshipUri = addRelationship(firstNode, secondNode, "singer", "{ \"from\" : \"1976\", \"until\" : \"1986\" }");

addRelationship メソッド内部では、Joe Strummer ノードのリレーションの URI を決定し、作成するリレーションの JSON 表現を POST しています。この JSON 表現には、エンドノード、リレーションのタイプを示すラベル、JSON コレクションとしてのリレーションの属性が含まれています。

private static URI addRelationship(URI startNode, URI endNode, String relationshipType, String jsonAttributes) throws URISyntaxException {
    URI fromUri = new URI(startNode.toString() + "/relationships");
    String relationshipJson = generateJsonRelationship(endNode, relationshipType, jsonAttributes);
     
    WebResource resource = Client.create().resource(fromUri);
    ClientResponse response = resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).entity(relationshipJson).post(ClientResponse.class); // リレーションの URI に JSON を POST
     
    System.out.println(String.format("POST to [%s], status code [%d], location header [%s]", fromUri, response.getStatus(), response.getLocation().toString()));
     
    return response.getLocation();
}

処理が適切に行われれば、201 Created ステータスコードと、新しく作成されたリレーションの URI を含む Location ヘッダが返されます。

[edit] リレーションへのプロパティの追加

ノードと同様、リレーションもプロパティを持つことができます。開発チームは Joe Strummer と The Clash の大ファンなので、リレーションにランク付けを追加して、The Clash というバンドを率いる Joe Strummer が 5 つ星のシンガーであることがわかるようにしてみましょう。

addMetadataToProperty(relationshipUri, "stars", "5");

addMetadataToProperty メソッド内部では、リレーションのプロパティの URI を決定し、新しい値を PUT しています (PUT なので、既存の値が上書きされることに注意してください)。

private static void addMetadataToProperty(URI relationshipUri, String name, String value) throws URISyntaxException {
    URI propertyUri = new URI(relationshipUri.toString() + "/properties");
    WebResource resource = Client.create().resource(propertyUri);
     
    String entity = toJsonNameValuePairCollection(name, value);
    ClientResponse response = resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).entity(entity).put(ClientResponse.class);
     
    System.out.println(String.format("PUT [%s] to [%s], status code [%d]", entity, propertyUri, response.getStatus()));
}

処理が適切に行われれば、サーバーから 200 OK レスポンスが返され (これは ClientResponse.getStatus() を呼び出すことでチェックできます)、非常に小さなグラフを作成できたことになります。このグラフに対して問い合わせを行うこともできます。

[edit] グラフに対する問い合わせ

Neo4j Server は、組み込みバージョンのデータベースの場合、グラフトラバーサルを使ってグラフ内のデータを参照します。現在、Neo4j Server は、実行するトラバーサルを示す JSON ペイロードが、当該トラバーサルの開始ノードに対して POST されることを期待しています (これはいずれ GET ベースのアプローチに変更される予定です)。

トラバーサルの処理を開始するために、ここでは自身を同等の JSON に変換できる簡単なクラスを使います。変換後の JSON はそのままサーバーに POST することができます。サンプルでは、タイプが "singer" の外向きのリレーションを持つすべてのノードを参照するトラバーサをハードコードしています。

private static void findSingersInBands(URI startNode) throws URISyntaxException {
    // TraversalDescription は、サーバーに送信する JSON に変換されます.
    TraversalDescription t = new TraversalDescription();
    t.setOrder(TraversalDescription.DEPTH_FIRST);
    t.setUniqueness(TraversalDescription.NODE);
    t.setMaxDepth(10);
    t.setReturnFilter(TraversalDescription.ALL);
    t.setRelationships(new Relationship("singer", Relationship.OUT));

トラバーサルのパラメータが決まったら、あとはこのトラバーサルを転送するだけです。それには、開始ノードのトラバーサの URI を決定し、次にトラバーサの JSON 表現をこの URI に POST します。

    URI traverserUri = new URI(startNode.toString() + "/traverse/node");
    WebResource resource = Client.create().resource(traverserUri);
    String jsonTraverserPayload = t.toJson();
    ClientResponse response = resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).entity(jsonTraverserPayload).post(ClientResponse.class);
     
    System.out.println(String.format("POST [%s] to [%s], status code [%d], returned data: " + System.getProperty("line.separator") + "%s", jsonTraverserPayload, traverserUri, response.getStatus(), response.getEntity(String.class)));
}

リクエストが完了すると、シンガーとシンガーが所属するバンドのデータセットが返されます。

[ {
  "outgoing_relationships" : "http://localhost:7474/db/data/node/82/relationships/out",
  "data" : {
    <<"band" : "The Clash">>,
    <<"name" : "Joe Strummer">>
  },
  "traverse" : "http://localhost:7474/db/data/node/82/traverse/{returnType}",
  "all_typed_relationships" : "http://localhost:7474/db/data/node/82/relationships/all/{-list|&|types}",
  "property" : "http://localhost:7474/db/data/node/82/properties/{key}",
  "all_relationships" : "http://localhost:7474/db/data/node/82/relationships/all",
  "self" : "http://localhost:7474/db/data/node/82",
  "properties" : "http://localhost:7474/db/data/node/82/properties",
  "outgoing_typed_relationships" : "http://localhost:7474/db/data/node/82/relationships/out/{-list|&|types}",
  "incoming_relationships" : "http://localhost:7474/db/data/node/82/relationships/in",
  "incoming_typed_relationships" : "http://localhost:7474/db/data/node/82/relationships/in/{-list|&|types}",
  "create_relationship" : "http://localhost:7474/db/data/node/82/relationships"
}, {
  "outgoing_relationships" : "http://localhost:7474/db/data/node/83/relationships/out",
  "data" : {
  },
  "traverse" : "http://localhost:7474/db/data/node/83/traverse/{returnType}",
  "all_typed_relationships" : "http://localhost:7474/db/data/node/83/relationships/all/{-list|&|types}",
  "property" : "http://localhost:7474/db/data/node/83/properties/{key}",
  "all_relationships" : "http://localhost:7474/db/data/node/83/relationships/all",
  "self" : "http://localhost:7474/db/data/node/83",
  "properties" : "http://localhost:7474/db/data/node/83/properties",
  "outgoing_typed_relationships" : "http://localhost:7474/db/data/node/83/relationships/out/{-list|&|types}",
  "incoming_relationships" : "http://localhost:7474/db/data/node/83/relationships/in",
  "incoming_typed_relationships" : "http://localhost:7474/db/data/node/83/relationships/in/{-list|&|types}",
  "create_relationship" : "http://localhost:7474/db/data/node/83/relationships"
} ]

[edit] その他の操作

REST API でどのようなことができるか、ざっと説明してきました。これまでの説明で見当がつくように、サーバー上に用意されている HTTP 操作はどれも簡単にラップすることができます。DELETE によるノードやリレーションの削除も同じです。すでに実行したサンプルを参考にすれば、Jersey クライアントで .post().put().delete() に置き換えることは簡単にできるはずです。

[edit] 次は

HTTP API は、クライアントライブラリを実装する場合に便利であるだけでなく、HTTP と REST に慣れたユーザーにも使い勝手のよいインタフェースです。ただし今後は、組み込みデータベースに対する言語バインディングと同様、さまざまな言語バインディングが登場し、REST API を活用するとともに、開発者にとって使いやすい各言語レベルの部品が提供されていくことになるでしょう。

Neo4j のサイト
ツールボックス