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 ファイルをダウンロードします。これは、選択内容に基づいて構成されたアプリケーションのアーカイブです。

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/<version>/libexec/logs/neo4j.log for current status.

Neo4j のデフォルトのユーザー名は neo4j、パスワードは neo4j です。ただし、新しいアカウントのパスワードを変更する必要があります。変更するには、http://localhost:7474 で Neo4j ブラウザーを開き、デフォルトの認証情報でログインしてください。新しいパスワードを設定するように求められます。

このガイドでは、新しいパスワードとして spring+neo4j を使用しています。これは本番環境では絶対に使用しないでください。この手順を完了すれば、ガイドの残りの部分を実行する準備が整います。

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

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

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

Neo4j はエンティティとその関連性を、どちらも同等の重要性を持って捉えます。たとえば、各人物のレコードを保存するシステムをモデリングしているとします。しかし、人物が誰を参照したか(この例では referrals)も追跡したいとします。Spring Data Neo4j を使えば、以下のリスト(src/main/java/com/example/accessingdataneo4j/Person.java)に示すように、いくつかの簡単なアノテーションでこれらすべてを捉えることができます。

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.jspecify.annotations.Nullable;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Relationship;

@Node
public class Person {

  @Id
  @GeneratedValue
  private @Nullable Long id;

  private String name;

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

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

  @Relationship(type = "REFERRED")
  public Set<Person> referrals;

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

  public String toString() {

    return this.name + " referred => "
      + Optional.ofNullable(this.referrals).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 クラスには @Node というアノテーションが付けられています。Neo4j がこれを保存すると、新しいノードが作成されます。このクラスには、@Id および @GeneratedValue というアノテーションが付けられた id も存在します。Neo4j は内部的にこのフィールドを使用してデータを追跡します。

次に重要なのは、referrals の集合です。これは単純な Set<Person> ですが、@Relationship としてマークされています。つまり、この集合のすべてのメンバーは、独立した Person ノードとしても存在すると想定されます。デフォルトの方向は OUTGOING であり、これは REFERRED 関連がこの人物から、その人物が紹介した各人物へと向けられていることを意味します。

referred() メソッドを使用すると、誰が誰を紹介したかを簡単に記録できます。

最後に、人の名前と紹介者を出力するための便利な 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.jspecify.annotations.Nullable;
import org.springframework.data.neo4j.repository.Neo4jRepository;

public interface PersonRepository extends Neo4jRepository<Person, Long> {

  @Nullable
  Person findByName(String name);

  List<Person> findByReferralsName(String name);
}

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

しかし、メソッドシグネチャーを宣言することで、他のクエリを定義することもできます。今回の例では、findByName を追加しました。これは、Person 型のノードを検索し、name に一致するものを見つけます。また、REFERRED の関連を逆順にトラバースする findByReferralsName もあります。つまり、人の名前を指定すると、その人を参照しているすべての Person ノードを返します。

Neo4j へのアクセス許可

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

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

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

実際の資格情報をソースリポジトリに保存しないでください。代わりに、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 を参照していることを示し、その後、彼を永続化します。関連の方向は OUTGOING であるため、この時点では Greg の発信 REFERRED 関連のみが保存されます。

次に、Neo4j から Roy を新しく取得し (そのノードの最新の状態を確保するため)、Craig を参照したことを記録してから、Roy を永続化します。

この例では、Craig にはまだ紹介者がいないため、Craig に追加のコードは必要ありません。各人物を反復処理し、情報をコンソールに出力すると、結果を確認できます。

最後に、過去を振り返って「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.referred(roy);
			greg.referred(craig);
			personRepository.save(greg);

			roy = personRepository.findByName(roy.getName());
			roy.referred(craig);
			personRepository.save(roy);

			// Craig has no referrals yet

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

			List<Person> referrers = personRepository.findByReferralsName(craig.getName());
			log.info("The following referred Craig...");
			referrers.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.0.1-SNAPSHOT.jar

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

java -jar target/gs-accessing-data-neo4j-0.0.1-SNAPSHOT.jar

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

Before linking up with Neo4j...
	Greg referred => []
	Roy referred => []
	Craig referred => []

Lookup each person by name...
	Greg referred => [Roy, Craig]
	Roy referred => [Craig]
	Craig referred => []

The following referred Craig...
	Greg
	Roy

出力結果から、(最初は)誰も誰かを紹介していないことがわかります。次に、関連性を確立すると、誰が誰を紹介したかがわかります。最後に、特定の人物を紹介したすべての人を検索する便利な逆引き検索が表示されます。

要約

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

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

関連事項

次のガイドも役立つかもしれません:

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

すべてのガイドは、コード用の ASLv2 ライセンス、およびドキュメント用の帰属表示、NoDerivatives クリエイティブコモンズライセンス (英語) でリリースされています。

コードを入手する