Neo4j でデータアクセス

このガイドでは、Spring Data Neo4j を使用して、グラフベースのデータベースである Neo4j (英語) にデータを保存および取得するアプリケーションを構築するプロセスを順を追って説明します。

構築するもの

Neo4j の NoSQL [Wikipedia] (英語) グラフベースのデータストアを使用して、組み込み Neo4j サーバーを構築し、エンティティと関連を保存し、クエリを開発します。

必要なもの

本ガイドの完成までの流れ

ほとんどの Spring 入門ガイドと同様に、最初から始めて各ステップを完了するか、すでに慣れている場合は基本的なセットアップステップをバイパスできます。いずれにしても、最終的に動作するコードになります。

最初から始めるには、Spring Initializr から開始に進みます。

基本スキップするには、次の手順を実行します。

完了したときは、gs-accessing-data-neo4j/complete のコードに対して結果を確認できます。

Spring Initializr から開始

IDE を使用する場合はプロジェクト作成ウィザードを使用します。IDE を使用せずにコマンドラインなどで開発する場合は、この事前に初期化されたプロジェクトからプロジェクトを ZIP ファイルとしてダウンロードできます。このプロジェクトは、このチュートリアルの例に合うように構成されています。

プロジェクトを手動で初期化するには:

  1. IDE のメニューまたはブラウザーから Spring Initializr を開きます。アプリケーションに必要なすべての依存関係を取り込み、ほとんどのセットアップを行います。

  2. Gradle または Maven のいずれかと、使用する言語を選択します。このガイドは、Java を選択したことを前提としています。

  3. 依存関係をクリックして、Spring Data Neo4j を選択します。

  4. 生成をクリックします。

  5. 結果の ZIP ファイルをダウンロードします。これは、選択して構成された Web アプリケーションのアーカイブです。

EclipseIntelliJ のような IDE は新規プロジェクト作成ウィザードから Spring Initializr の機能が使用できるため、手動での ZIP ファイルのダウンロードやインポートは不要です。
プロジェクトを GitHub からフォークして、IDE または他のエディターで開くこともできます。

Neo4j サーバーを立ち上げる

このアプリケーションを構築する前に、Neo4j サーバーをセットアップする必要があります。

Neo4j には、フリーでインストールできるオープンソースサーバーがあり、Docker で実行することもできます。

Homebrew がインストールされている Mac にサーバーをインストールするには、次のコマンドを実行します。

$ brew install neo4j

他のオプションについては、https://neo4j.com/download/community-edition/ (英語) にアクセスしてください。

インストールしたら、次のコマンドを実行してデフォルト設定で起動します。

$ neo4j start

次のような出力が表示されます。

Starting Neo4j.
Started neo4j (pid 96416). By default, it is available at http://localhost:7474/
There may be a short delay until the server is ready.
See /usr/local/Cellar/neo4j/3.0.6/libexec/logs/neo4j.log for current status.

デフォルトでは、Neo4j のユーザー名とパスワードは neo4j と neo4j です。ただし、新しいアカウントのパスワードを変更する必要があります。これを行うには、次のコマンドを実行します。

curl -v -u neo4j:neo4j POST localhost:7474/user/neo4j/password -H "Content-type:application/json" -d "{\"password\":\"secret\"}"

これにより、パスワードが neo4j から secret に変更されます。これは、本番環境で実行しないことです! その手順が完了すると、このガイドの残りの部分を実行する準備ができているはずです。

または、Neo4j Docker イメージ (英語) で実行します。パスワードは NEO4J_AUTH 環境変数で変更できます。

docker run \
  --publish=7474:7474 --publish=7687:7687 \
  --volume=$HOME/neo4j/data:/data \
  --env NEO4J_AUTH=neo4j/password
  neo4j

単純なエンティティを定義する

Neo4j は、エンティティとその関連をキャプチャーします。両方の側面が同じように重要です。各人のレコードを保存するシステムをモデル化していると想像してください。ただし、人の同僚(この例では teammates)も追跡する必要があります。次のリスト(src/main/java/com/example/accessingdataneo4j/Person.java)に示すように、Spring Data Neo4j を使用すると、いくつかの簡単なアノテーションを使用してすべてをキャプチャーできます。

package com.example.accessingdataneo4j;

import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Property;
import org.springframework.data.neo4j.core.schema.Relationship;
import org.springframework.data.neo4j.core.schema.GeneratedValue;

@Node
public class Person {

  @Id @GeneratedValue private Long id;

  private String name;

  private Person() {
    // Empty constructor required as of Neo4j API 2.0.5
  };

  public Person(String name) {
    this.name = name;
  }

  /**
   * Neo4j doesn't REALLY have bi-directional relationships. It just means when querying
   * to ignore the direction of the relationship.
   * https://dzone.com/articles/modelling-data-neo4j
   */
  @Relationship(type = "TEAMMATE")
  public Set<Person> teammates;

  public void worksWith(Person person) {
    if (teammates == null) {
      teammates = new HashSet<>();
    }
    teammates.add(person);
  }

  public String toString() {

    return this.name + "'s teammates => "
      + Optional.ofNullable(this.teammates).orElse(
          Collections.emptySet()).stream()
            .map(Person::getName)
            .collect(Collectors.toList());
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

ここに、name という 1 つの属性のみを持つ Person クラスがあります。

Person クラスには @NodeEntity のアノテーションが付けられています。Neo4j が保存すると、新しいノードが作成されます。このクラスには、@GraphId とマークされた id もあります。Neo4j は、@GraphId を内部的に使用してデータを追跡します。

次の重要な部分は teammates のセットです。単純な Set<Person> ですが、@Relationship としてマークされています。これは、このセットのすべてのメンバーが個別の Person ノードとしても存在することが期待されることを意味します。方向が UNDIRECTED に設定されていることに注意してください。これは、TEAMMATE 関連を照会するときに、Spring Data Neo4j が関連の方向を無視することを意味します。

worksWith() メソッドを使用すると、人々を簡単にリンクできます。

最後に、人の名前とその人の同僚を出力する便利な toString() メソッドがあります。

単純なクエリを作成する

Spring Data Neo4j は、Neo4j でのデータの保存に重点を置いています。ただし、クエリを派生させる機能など、Spring Data Commons プロジェクトから機能を継承します。基本的に、Neo4j のクエリ言語を学ぶ必要はありません。代わりに、いくつかのメソッドを記述して、クエリを記述させることができます。

これがどのように機能するかを確認するには、Person ノードを照会するインターフェースを作成します。次のリスト(src/main/java/com/example/accessingdataneo4j/PersonRepository.java 内)は、そのような照会を示しています。

package com.example.accessingdataneo4j;

import java.util.List;
import org.springframework.data.neo4j.repository.Neo4jRepository;

public interface PersonRepository extends Neo4jRepository<Person, Long> {

  Person findByName(String name);
  List<Person> findByTeammatesName(String name);
}

PersonRepository は Neo4jRepository インターフェースを継承し、動作する型 Person にプラグインします。このインターフェースには、標準の CRUD(作成、読み取り、更新、削除)操作を含む多くの操作が付属しています。

ただし、メソッドシグネチャーを宣言することで、他のクエリを定義できます。この場合、findByName を追加しました。これは、型 Person のノードを検索し、name で一致するノードを見つけます。Person ノードを検索し、teammates フィールドの各エントリにドリルし、チームメイトの name に基づいて一致する findByTeammatesName もあります。

Neo4j へのアクセス許可

Neo4j Community エディションにアクセスするには資格情報が必要です。次のように、これらの資格情報を構成するには(src/main/resources/application.properties で)いくつかのプロパティを設定します。

spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret

これには、デフォルトのユーザー名(neo4j)と以前に選択した新しく設定されたパスワード(secret)が含まれます。

実際の資格情報をソースリポジトリに保存しないでください。代わりに、Spring Boot のプロパティのオーバーライドを使用してランタイムで構成します。

これを適切に配置すると、これを接続して、どのように見えるかを確認できます!

アプリケーションクラスを作成する

Spring Initializr は、アプリケーションの単純なクラスを作成します。次のリストは、Initializr がこの例(src/main/java/com/example/accessingdataneo4j/AccessingDataNeo4jApplication.java 内)で作成したクラスを示しています。

package com.example.accessingdataneo4j;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AccessingDataNeo4jApplication {

  public static void main(String[] args) {
    SpringApplication.run(AccessingDataNeo4jApplication.class, args);
  }

}

@SpringBootApplication は、次のすべてを追加する便利なアノテーションです。

  • @Configuration: アプリケーションコンテキストの Bean 定義のソースとしてクラスにタグを付けます。

  • @EnableAutoConfiguration: クラスパス設定、他の Bean、さまざまなプロパティ設定に基づいて Bean の追加を開始するよう Spring Boot に指示します。例: spring-webmvc がクラスパスにある場合、このアノテーションはアプリケーションに Web アプリケーションとしてフラグを立て、DispatcherServlet のセットアップなどの主要な動作をアクティブにします。

  • @ComponentScan: Spring に、com/example パッケージ内の他のコンポーネント、構成、サービスを探して、コントローラーを検出させるように指示します。

main() メソッドは、Spring Boot の SpringApplication.run() メソッドを使用してアプリケーションを起動します。XML が 1 行もないことに気付きましたか? web.xml ファイルもありません。この Web アプリケーションは 100% 純粋な Java であり、接続機能やインフラストラクチャの構成に対処する必要はありませんでした。

Spring Boot は、@SpringBootApplication クラスの同じパッケージ(またはサブパッケージ)に含まれている限り、これらのリポジトリを自動的に処理します。登録プロセスをさらに制御するには、@EnableNeo4jRepositories アノテーションを使用できます。

デフォルトでは、@EnableNeo4jRepositories は現在のパッケージをスキャンして、Spring Data のリポジトリインターフェースの 1 つを継承するインターフェースを探します。プロジェクトレイアウトに複数のプロジェクトがあり、リポジトリが見つからない場合は、basePackageClasses=MyRepository.class を使用して、型ごとに異なるルートパッケージをスキャンするように Spring Data Neo4j に安全に指示できます。

ロギング出力が表示されます。サービスは数秒以内に起動して実行されるはずです。

ここで、前に定義した PersonRepository のインスタンスをオートワイヤーします。Spring Data Neo4j はそのインターフェースを動的に実装し、インターフェースの義務を果たすために必要なクエリコードをプラグインします。

main メソッドは、Spring Boot の SpringApplication.run() を使用してアプリケーションを起動し、関連を構築する CommandLineRunner を呼び出します。

この場合、3 つのローカル Person インスタンスを作成します: Greg、Roy、Craig。最初は、メモリにのみ存在します。誰のチームメイトでもないことに注意してください(まだ)。

最初に、Greg を見つけ、彼が Roy および Craig で動作することを示してから、再び彼を保持します。チームメイト関連は UNDIRECTED (つまり、双方向)としてマークされていたことを思い出してください。これは、Roy と Craig も更新されたことを意味します。

それが、Roy を更新する必要があるときの理由です。Neo4j からそのレコードを最初に取得することが重要です。Craig をリストに追加する前に、Roy のチームメイトの最新のステータスが必要です。

Craig をフェッチして関連を追加するコードがないのはなぜですか? すでに持っているからです! Greg は以前に Craig をチームメイトとしてタグ付けし、Roy も同様にタグ付けしました。つまり、Craig の関連を再度更新する必要はありません。各チームメンバーを反復処理し、その情報をコンソールに出力するときに表示できます。

最後に、後方を見る他のクエリをチェックして、「誰と誰が連携するのか」という質問に回答します。

次のリストは、完成した AccessingDataNeo4jApplication クラス(src/main/java/com/example/accessingdataneo4j/AccessingDataNeo4jApplication.java で)を示しています。

package com.example.accessingdataneo4j;

import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;

@SpringBootApplication
@EnableNeo4jRepositories
public class AccessingDataNeo4jApplication {

	private final static Logger log = LoggerFactory.getLogger(AccessingDataNeo4jApplication.class);

	public static void main(String[] args) throws Exception {
		SpringApplication.run(AccessingDataNeo4jApplication.class, args);
		System.exit(0);
	}

	@Bean
	CommandLineRunner demo(PersonRepository personRepository) {
		return args -> {

			personRepository.deleteAll();

			Person greg = new Person("Greg");
			Person roy = new Person("Roy");
			Person craig = new Person("Craig");

			List<Person> team = Arrays.asList(greg, roy, craig);

			log.info("Before linking up with Neo4j...");

			team.stream().forEach(person -> log.info("\t" + person.toString()));

			personRepository.save(greg);
			personRepository.save(roy);
			personRepository.save(craig);

			greg = personRepository.findByName(greg.getName());
			greg.worksWith(roy);
			greg.worksWith(craig);
			personRepository.save(greg);

			roy = personRepository.findByName(roy.getName());
			roy.worksWith(craig);
			// We already know that roy works with greg
			personRepository.save(roy);

			// We already know craig works with roy and greg

			log.info("Lookup each person by name...");
			team.stream().forEach(person -> log.info(
					"\t" + personRepository.findByName(person.getName()).toString()));

			List<Person> teammates = personRepository.findByTeammatesName(greg.getName());
			log.info("The following have Greg as a teammate...");
			teammates.stream().forEach(person -> log.info("\t" + person.getName()));
		};
	}

}

実行可能 JAR を構築する

コマンドラインから Gradle または Maven を使用してアプリケーションを実行できます。必要なすべての依存関係、クラス、リソースを含む単一の実行可能 JAR ファイルを構築して実行することもできます。実行可能な jar を構築すると、開発ライフサイクル全体、さまざまな環境などで、アプリケーションとしてサービスを簡単に提供、バージョン管理、デプロイできます。

Gradle を使用する場合、./gradlew bootRun を使用してアプリケーションを実行できます。または、次のように、./gradlew build を使用して JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar build/libs/gs-accessing-data-neo4j-0.1.0.jar

Maven を使用する場合、./mvnw spring-boot:run を使用してアプリケーションを実行できます。または、次のように、./mvnw clean package で JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar target/gs-accessing-data-neo4j-0.1.0.jar
ここで説明する手順は、実行可能な JAR を作成します。クラシック WAR ファイルを作成することもできます。

次のリストに似たものが表示されるはずです(クエリなどの他のものも同様)。

Before linking up with Neo4j...
	Greg's teammates => []
	Roy's teammates => []
	Craig's teammates => []

Lookup each person by name...
	Greg's teammates => [Roy, Craig]
	Roy's teammates => [Greg, Craig]
	Craig's teammates => [Roy, Greg]

出力から、(最初は)関連によって誰も接続されていないことがわかります。次に、ユーザーを追加すると、それらは結び付けられます。最後に、チームメイトに基づいて人々を検索する便利なクエリを見ることができます。

要約

おめでとう! 組み込み Neo4j サーバーをセットアップし、いくつかの簡単な関連エンティティを保存し、いくつかのクイッククエリを開発しました。

Neo4j リポジトリをハイパーメディアベースの RESTful フロントエンドで簡単に公開したい場合は、Spring Data REST API の自動生成 (Neo4j) を参照してください。

関連事項

次のガイドも役立ちます。

新しいガイドを作成したり、既存のガイドに貢献したいですか? 投稿ガイドラインを参照してください [GitHub] (英語)

すべてのガイドは、コード用の ASLv2 ライセンス、およびドキュメント用の Attribution、NoDerivatives creative commons ライセンス (英語) でリリースされています。

コードを入手する