mcpField
S
spring-data-neo4j

spring-data-neo4j

github.com/giuseppe-trisciuoglioデータ・データベース3653.7k 回インストール7 週間前に更新
リポジトリ

spring-data-neo4j について

Spring Boot アプリケーションに Spring Data Neo4j を統合するためのスキルです。グラフエンティティのマッピング、命令型・リアクティブリポジトリ、Cypher クエリ、組み込みデータベースでのテストを扱います。

主な機能

  1. 01ノードとリレーションシップのマッピング
  2. 02命令型・リアクティブリポジトリ
  3. 03@Query によるカスタム Cypher クエリ
  4. 04Neo4j Harness を使った統合テスト

活用シーン

  1. 01Spring Boot アプリへの Neo4j グラフストレージ導入
  2. 02カスタム Cypher による関連ノードの検索
  3. 03組み込みデータベースを使ったリポジトリのテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill spring-data-neo4j

インストール後、Claude Code で/spring-data-neo4jを入力して読み込まれたか確認します。

同梱ファイルreferences/examples.mdreferences/reference.md

詳細

Spring Data Neo4j 統合パターン

概要

Spring Boot アプリケーション向けの Spring Data Neo4j 統合パターンを提供します。@Node と @Relationship によるノードエンティティのマッピング、リポジトリの設定(命令型およびリアクティブ)、@Query を使ったカスタム Cypher クエリ、組み込み Neo4j データベースによる統合テストを扱います。

使用する場面

次の作業を行う際に、このスキルを使用してください。

  • Spring Boot でのグラフデータベースと Neo4j の統合
  • ノードエンティティ、リレーションシップ、Cypher クエリ
  • Spring Data Neo4j リポジトリ(命令型またはリアクティブ)
  • 組み込みデータベースを使用した Neo4j のテスト

手順

1. Spring Data Neo4j をセットアップする

依存関係を追加します。

Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>

Gradle:

implementation 'org.springframework.boot:spring-boot-starter-data-neo4j'

application.properties で接続を設定します。

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

Cypher-DSL の方言を設定します(推奨)。

@Configuration
public class Neo4jConfig {
    @Bean
    Configuration cypherDslConfiguration() {
        return Configuration.newConfig()
            .withDialect(Dialect.NEO4J_5).build();
    }
}

検証チェックポイント:作業を進める前に、cypher-shell で MATCH (n) RETURN count(n) を実行し、接続が機能することを確認してください。

2. ノードエンティティを定義する
  1. @Node アノテーションを使用してエンティティクラスを指定します
  2. ID の戦略を選択します。
    • ビジネスキーを @Id として使用します(不変の自然識別子)
    • 生成された @Id @GeneratedValue を使用します(Neo4j の内部 ID)
  3. @Relationship アノテーションでリレーションシップを定義します
  4. final フィールドを使ってエンティティを不変にします
  5. カスタムのプロパティ名には @Property を使用します

検証チェックポイント:エンティティの保存に失敗する場合は、制約違反がないか確認してください。ID の重複は一意性制約違反になります。

3. リポジトリを作成する
  1. リポジトリインターフェースを拡張します。
    • 命令型の操作には Neo4jRepository<Entity, ID>
    • リアクティブな操作には ReactiveNeo4jRepository<Entity, ID>
  2. シンプルなクエリにはクエリ導出を使用します
  3. 複雑な Cypher クエリには @Query アノテーションを適用します
  4. パラメーターには $paramName 構文を使用します

検証チェックポイント:まず findAll() でリポジトリをテストしてください。結果が空の場合は、Neo4j インスタンスが稼働していることと、認証情報が正しいことを確認してください。

4. 実装をテストする
  1. テストスライスを使用したリポジトリのテストには @DataNeo4jTest を使用します
  2. 組み込みデータベースとフィクスチャを使って Neo4j Harness をセットアップします
  3. withFixture() の Cypher クエリでテストデータを用意します
  4. テスト間でテストデータをクリーンアップします

検証チェックポイント:「Connection refused」でテストが失敗する場合は、@BeforeAll で組み込み Neo4j が正常に起動したことを確認してください。

基本的なエンティティマッピング

ビジネスキーを使ったノードエンティティ
@Node("Movie")
public class MovieEntity {

    @Id
    private final String title;  // Business key as ID

    @Property("tagline")
    private final String description;

    private final Integer year;

    @Relationship(type = "ACTED_IN", direction = Direction.INCOMING)
    private List<Roles> actorsAndRoles = new ArrayList<>();

    @Relationship(type = "DIRECTED", direction = Direction.INCOMING)
    private List<PersonEntity> directors = new ArrayList<>();

    public MovieEntity(String title, String description, Integer year) {
        this.title = title;
        this.description = description;
        this.year = year;
    }
}
生成 ID を使ったノードエンティティ
@Node("Movie")
public class MovieEntity {

    @Id @GeneratedValue
    private Long id;

    private final String title;

    @Property("tagline")
    private final String description;

    public MovieEntity(String title, String description) {
        this.id = null;  // Never set manually
        this.title = title;
        this.description = description;
    }

    // Wither method for immutability with generated IDs
    public MovieEntity withId(Long id) {
        if (this.id != null && this.id.equals(id)) {
            return this;
        } else {
            MovieEntity newObject = new MovieEntity(this.title, this.description);
            newObject.id = id;
            return newObject;
        }
    }
}

リポジトリパターン

基本的なリポジトリインターフェース
@Repository
public interface MovieRepository extends Neo4jRepository<MovieEntity, String> {

    // Query derivation from method name
    MovieEntity findOneByTitle(String title);

    List<MovieEntity> findAllByYear(Integer year);

    List<MovieEntity> findByYearBetween(Integer startYear, Integer endYear);
}
リアクティブリポジトリ
@Repository
public interface MovieRepository extends ReactiveNeo4jRepository<MovieEntity, String> {

    Mono<MovieEntity> findOneByTitle(String title);

    Flux<MovieEntity> findAllByYear(Integer year);
}

命令型とリアクティブの比較:

  • ブロッキングの命令型操作には Neo4jRepository を使用します
  • ノンブロッキングのリアクティブ操作には ReactiveNeo4jRepository を使用します
  • 同じアプリケーション内で命令型とリアクティブを混在させないでください
  • リアクティブ操作には、データベース側で Neo4j 4 以降が必要です

@Query を使ったカスタムクエリ

@Repository
public interface AuthorRepository extends Neo4jRepository<Author, Long> {

    @Query("MATCH (b:Book)-[:WRITTEN_BY]->(a:Author) " +
           "WHERE a.name = $name AND b.year > $year " +
           "RETURN b")
    List<Book> findBooksAfterYear(@Param("name") String name,
                                   @Param("year") Integer year);

    @Query("MATCH (b:Book)-[:WRITTEN_BY]->(a:Author) " +
           "WHERE a.name = $name " +
           "RETURN b ORDER BY b.year DESC")
    List<Book> findBooksByAuthorOrderByYearDesc(@Param("name") String name);
}

カスタムクエリのベストプラクティス:

  • パラメータのプレースホルダーには $parameterName を使用します
  • パラメーター名がメソッドのパラメーターと異なる場合は、@Param アノテーションを使用します
  • MATCH はノードのパターンとリレーションシップを指定します
  • WHERE は結果を絞り込みます
  • RETURN は返す内容を定義します

テスト戦略

統合テストのための Neo4j Harness

テスト設定:

@DataNeo4jTest
class BookRepositoryIntegrationTest {

    private static Neo4j embeddedServer;

    @BeforeAll
    static void initializeNeo4j() {
        embeddedServer = Neo4jBuilders.newInProcessBuilder()
            .withDisabledServer()  // No HTTP access needed
            .withFixture(
                "CREATE (b:Book {isbn: '978-0547928210', " +
                "name: 'The Fellowship of the Ring', year: 1954})" +
                "-[:WRITTEN_BY]->(a:Author {id: 1, name: 'J. R. R. Tolkien'}) " +
                "CREATE (b2:Book {isbn: '978-0547928203', " +
                "name: 'The Two Towers', year: 1956})" +
                "-[:WRITTEN_BY]->(a)"
            )
            .build();
    }

    @AfterAll
    static void stopNeo4j() {
        embeddedServer.close();
    }

    @DynamicPropertySource
    static void neo4jProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.neo4j.uri", embeddedServer::boltURI);
        registry.add("spring.neo4j.authentication.username", () -> "neo4j");
        registry.add("spring.neo4j.authentication.password", () -> "null");
    }

    @Autowired
    private BookRepository bookRepository;

    @Test
    void givenBookExists_whenFindOneByTitle_thenBookIsReturned() {
        Book book = bookRepository.findOneByTitle("The Fellowship of the Ring");
        assertThat(book.getIsbn()).isEqualTo("978-0547928210");
    }
}

例

例 1:エンティティの保存と取得

入力:

MovieEntity movie = new MovieEntity("The Matrix", "Welcome to the Real World", 1999);
movieRepository.save(movie);

MovieEntity found = movieRepository.findOneByTitle("The Matrix");

出力:

MovieEntity{
    title="The Matrix",
    description="Welcome to the Real World",
    year=1999,
    actorsAndRoles=[],
    directors=[]
}
例 2:カスタム Cypher クエリ

入力:

List<Book> books = authorRepository.findBooksAfterYear("J.R.R. Tolkien", 1950);

出力:

[
    Book{isbn="978-0547928210", name="The Fellowship of the Ring", year=1954},
    Book{isbn="978-0547928203", name="The Two Towers", year=1956},
    Book{isbn="978-0547928227", name="The Return of the King", year=1957}
]
例 3:リレーションシップのたどり方

入力:

@Query("MATCH (m:Movie)<-[:ACTED_IN]-(a:Person) " +
       "WHERE m.title = $title RETURN a.name as actorName")
List<String> findActorsByMovieTitle(@Param("title") String title);

List<String> actors = movieRepository.findActorsByMovieTitle("The Matrix");

出力:

["Keanu Reeves", "Laurence Fishburne", "Carrie-Anne Moss", "Hugo Weaving"]

基本から応用まで、映画データベース全体、ソーシャルネットワークのパターン、E コマースの商品カタログ、カスタムクエリ、リアクティブ操作を網羅する例を段階的に紹介します。

包括的なコード例については、examples を参照してください。

ベストプラクティス

エンティティ設計
  • 不変エンティティを使用し、フィールドは final にします
  • ビジネスキー(@Id)または生成 ID(@Id @GeneratedValue)を選択します
  • エンティティはグラフ構造に集中させ、ビジネスロジックは含めません
  • 適切なリレーションシップの方向(INCOMING、OUTGOING、UNDIRECTED)を使用します
リポジトリ設計
  • 命令型の場合は Neo4jRepository、リアクティブの場合は ReactiveNeo4jRepository を拡張します
  • 単純なクエリにはクエリ導出を使用します
  • 複雑なグラフパターンにはカスタムの @Query を記述します
  • 同じアプリケーション内で命令型とリアクティブを混在させないでください
設定
  • Cypher-DSL の方言は必ず明示的に設定します
  • 認証情報には環境固有のプロパティを使用します
  • ソースコードに認証情報をハードコードしないでください
  • 負荷に応じてコネクションプールを設定します
テスト
  • 統合テストには Neo4j Harness を使用します
  • withFixture() Cypher クエリでテストデータを用意します
  • テストのスライスには @DataNeo4jTest を使用します
  • 成功するケースとエッジケースの両方をテストします
アーキテクチャ
  • コンストラクターインジェクションのみを使用します
  • ドメインエンティティと DTO を分離します
  • 機能ベースのパッケージ構成に従います
  • ドメイン層をフレームワークに依存しない状態に保ちます
セキュリティ
  • 認証情報には Spring Boot のプロパティ上書きを使用します
  • 適切な認証と認可を設定します
  • サービス層で入力パラメーターを検証します
  • Cypher インジェクションを防ぐため、パラメーター化されたクエリを使用します

制約と警告

  • 同じアプリケーション内で命令型とリアクティブのリポジトリを混在させないでください。
  • 書き込み操作には Neo4j のトランザクションが必要です。@Transactional が適切に設定されていることを確認してください。
  • 深いリレーションシップのトラバーサルはパフォーマンスの問題を引き起こす可能性があるため、注意してください。
  • メモリーの問題を避けるため、大量の結果セットはページネーションしてください。
  • Cypher クエリでは大文字と小文字が区別されます。プロパティ名の大文字と小文字を一貫させてください。
  • 不変エンティティでは、生成 ID に適切な wither メソッドが必要です。
  • Spring Data Neo4j のリレーションシップはデフォルトでは遅延ロードされません。大規模なグラフではプロジェクションを検討してください。
  • Neo4j Java ドライバーはリアクティブストリームと互換性がありません。リアクティブ操作にはリアクティブドライバーを使用してください。

トラブルシューティング

問題原因解決策
localhost:7687 で Connection refusedNeo4j サーバーが起動していないNeo4j を起動するか、テストに組み込み Neo4j を使用します
Authentication failed認証情報が間違っているspring.neo4j.authentication.username/password を確認します
エンティティが保存されない/MATCH が何も返さないトランザクションがコミットされていない@Transactional を追加するか、自動コミットの設定を確認します
保存時に ConstraintViolationException@Id の値が重複しているID が一意であることを確認するか、@GeneratedValue を使用します
結果にリレーションシップが含まれない@Relationship の方向が間違っているDirection.INCOMING/OUTGOING/UNDIRECTED を確認します
@Query が誤ったデータを返すCypher のパラメーター構文が間違っている$paramName を使用し、$ {paramName} は使用しません
@DataNeo4jTest でテストが失敗する組み込み Neo4j が起動していないテストの前に @BeforeAll で Neo4j が起動することを確認します

参考資料

API リファレンス全体、Cypher クエリパターン、設定オプションなどの詳細なドキュメントについては、次を参照してください。

外部リソース

コメント