Spring Data Neo4j (SDN)

repository·main·Indexed 21 days ago

https://github.com/spring-projects/spring-data-neo4j

A consistent, Spring-based programming model for integrating with Neo4j graph databases. It supports both imperative and reactive programming models, offering lightweight mapping, support for immutable entities, and integration with Spring Boot via the spring-boot-starter-data-neo4j starter.

Tokens
27.5K
Snippets
64
Records
104
Agent score
74%

What's inside Spring Data Neo4j

  1. What is Spring Data Neo4j (SDN)?

    main

    Spring Data Neo4j (SDN) is an Object-Graph-Mapping (OGM) library designed for Spring and Spring Boot applications (and some Jakarta EE environments). It maps nodes and relationships in a Neo4j graph to objects and references in a domain model.

    Key characteristics:

    • Direct Driver Usage: SDN relies entirely on the neo4j-java-driver (Bolt protocol) without adding an intermediate driver layer.
    • Integrated OGM: Unlike previous versions (SDN 5/SDN+OGM), the object mapping framework is built directly into SDN. It does not require or support a separate OGM implementation.
    • Modern Feature Support: Provides full support for immutable entities (e.g., Kotlin data classes) and the Spring reactive programming model.
    • Transaction Integration: High-level abstractions like repositories, templates, and clients are fully integrated with Spring's application transactions.
  2. Overview of Spring Data Neo4j Mapping Annotations

    main

    Spring Data Neo4j (SDN) uses annotations to map Java/Kotlin objects to Neo4j nodes and relationships. While mapping can work with plain POJOs without annotations, using them allows the classpath scanner to pre-process metadata, improving performance by avoiding runtime metadata construction during the first save operation.

    SDN Annotations

    • @Node: Marks a class as a candidate for mapping to a node.
    • @Id: Marks the field used for identity.
    • @GeneratedValue: Used with @Id to specify identifier generation strategy.
    • @Property: Modifies the mapping from a class attribute to a specific Neo4j property name.
    • @CompositeProperty: Used for Map type attributes to be read back as a composite.
    • @Relationship: Specifies details of a relationship (type and direction).
    • @DynamicLabels: Marks a collection (e.g., List<String>) as a source for runtime-managed labels.
    • @RelationshipProperties: Marks a class as the target for properties residing on a relationship.
    • @TargetNode: Marks the target of a relationship within a @RelationshipProperties class.

    Spring Data Commons Annotations

    • @Id: (Same as SDN @Id)
    • @CreatedBy / @CreatedDate: Auditing annotations for creator info.
    • @LastModifiedBy / @LastModifiedDate: Auditing annotations for modifier info.
    • @PersistenceCreator: Marks the preferred constructor for reading entities.
    • @Persistent: Marks a class as a candidate for mapping (similar to @Node).
    • @Version: Used for optimistic locking.
    • @ReadOnlyProperty: Marks a property as read-only (hydrated on read, but not written).
  3. What is the Neo4jClient and when to use it

    main

    The Neo4jClient is a thin layer on top of the Neo4j Java driver provided by Spring Data Neo4j (SDN). It is designed to integrate with Spring's transaction management (both imperative and reactive) and participate in JTA-Transactions if necessary, without adding mapping overhead.

    There are two distinct flavors of the client:

    1. org.springframework.data.neo4j.core.Neo4jClient: For imperative (blocking) scenarios.
    2. org.springframework.data.neo4j.core.ReactiveNeo4jClient: For reactive (non-blocking) scenarios.

    While they share a similar fluent API vocabulary, they are not API compatible. The imperative client returns Optional<T> or Collection<T>, whereas the reactive client returns publishers like Mono<T> or Flux<T>.

  4. How SDN creates queries for Load operations

    main

    When loading data, SDN uses Cypher map projections and pattern comprehensions to ensure only the properties and relationships defined in your Java model are queried.

    Key Components of a Load Query:

    • ID Mapping: SDN uses a special field ${neo4jInternalId} (which resolves to id(n) or your custom ID) to map database nodes back to Java objects.
    • Label Mapping: The ${neo4jLabels} field (resolving to labels(n)) is used to handle inheritance and ensure nodes are mapped to the correct concrete classes.
    • Relationship Mapping: Relationships are returned using pattern comprehensions.

    Example Map Projection:

    RETURN n{.first_name, .personNumber, {neo4jInternalId}: id(n), {neo4jLabels}: labels(n)}

    Example with Relationships:

    RETURN n{.first_name, ..., Person_Has_Hobby: [(n)-[:Has]->(n_hobbies:Hobby)|n_hobbies{{neo4jInternalId}: id(n_hobbies), .name, {neo4jLabels}: labels(n_hobbies)}]}

    Handling Cycles: If your schema contains self-referencing nodes or potential cycles, SDN falls back to a cascading/data-driven query creation. It executes an initial query and then iteratively executes further queries on the fly for discovered relationships until no new nodes or relationships are found.

    // Example of a load query with map projection and pattern comprehension
    RETURN n{.first_name, ..., Person_Has_Hobby: [(n)-[:Has]->(n_hobbies:Hobby)|n_hobbies{{neo4jInternalId}: id(n_hobbies), .name, {neo4jLabels}: labels(n_hobbies)}]}
  5. Configure Id-Mapping strategies in Spring Data Neo4j

    main

    Spring Data Neo4j (SDN) allows you to define how entity identifiers (Ids) are mapped to Neo4j nodes. You can use the standard @org.springframework.data.annotation.Id to mark an attribute as an identifier, but to control the mapping behavior, you should use the SDN-specific @org.springframework.data.neo4j.core.schema.Id annotation.

    There are three available strategies for Id mapping:

    1. internal: The identifier is mapped to the native Neo4j id(node). This is the default strategy.
    2. assigned: The identifier is provided by an external source (e.g., your application logic).
    3. generated: The identifier is generated by the system. This strategy requires an additional generator attribute to be specified.
  6. Understand the Spring Data programming model

    main

    Spring Data applies a consistent programming model across various data stores (such as Neo4j, JPA, JDBC, and MongoDB). To use Spring Data Neo4j effectively, you should be familiar with core Spring Framework concepts, specifically:

    • Inversion of Control (IoC): The fundamental concept of how Spring manages object lifecycles and dependencies.
    • Type Conversion System: How Spring handles data transformation between different types.
    • Expression Language (SpEL): Used for dynamic configuration and querying.
    • DAO Exception Hierarchy: How data access exceptions are structured and translated.

    Because the programming model is unified, learning how to work with Spring Data repositories is a transferable skill across different database technologies.

  7. Configure Aggregate Boundaries on Entities

    main

    To avoid the complexity of deep multi-level projections, you can define Aggregate Boundaries at the entity level.

    By supplying one or more classes to the aggregateBoundary parameter on an entity, you instruct SDN to treat those classes as boundaries. For these parameterized entities, SDN will only report the @Id field and will not follow relationships or fetch other properties.

    Note: You can still use interface-based projections on these entities to access specific properties or relationships that fall within the declared boundaries.

  8. How SDN creates queries for Save operations

    main

    When you perform a save operation, Spring Data Neo4j (SDN) issues multiple Cypher statements to synchronize the database graph with your Java domain model.

    The Save Process:

    1. Upsert Node: A UNION statement is used to either CREATE a node (if the identifier is not found) or UPDATE properties (if the node exists).
    2. Relationship Cleanup: If the entity is not new, SDN removes all existing relationships of the first found type defined in your domain model to ensure the graph matches the Java state.
    3. Related Entity Upsert: The related entity is created/updated using the same logic as the root entity.
    4. Relationship Creation: The relationship itself is created using a MERGE statement.
    5. Recursion: This process repeats for all defined relationships and nested related entities.

    WARNING: Because SDN removes existing relationships to sync the model, you should avoid loading, manipulating, and saving sub-graphs. Doing so may cause relationships in the database that are not present in your partial Java model to be deleted.

    // Example of the UNION statement used for upserting a node
    OPTIONAL MATCH (hlp:Person) WHERE id(hlp) = ${neo4jId} 
    WITH hlp WHERE hlp IS NULL 
    CREATE (n:Person) SET n = ${neo4jProperties} 
    RETURN id(n) 
    UNION 
    MATCH (n) WHERE id(n) = ${neo4jId} 
    SET n = ${neo4jProperties} 
    RETURN id(n)
  9. Choose a querying mechanism in Spring Data Neo4j

    main

    Spring Data Neo4j provides several ways to interact with your Neo4j database, depending on the level of abstraction required. All of these mechanisms support reactive programming:

    • Neo4j Repositories: The highest level of abstraction, following the standard Spring Data repository pattern.
    • Neo4j Template: Provides a more flexible, template-based approach for interacting with the database.
    • Neo4j Client: A lower-level API for direct interaction with the database.

    Note that while the reactive variants of these tools support most repository features, they differ from their imperative counterparts in their paging mechanisms.

  10. Using a business key as an ID

    main

    A business key (or natural key) is a stable, unique attribute of the domain (e.g., a person's name or email) used as the primary identifier.

    Implementation Note: Business keys must be set on the domain entity before Spring Data Neo4j processes it. Because the key is present before processing, SDN cannot automatically determine if the entity is new or existing based on the ID alone. To enable proper new/existing detection, you must also provide a @Version field.

    Pros and Cons:

    • Pros: Feels natural to the domain model; the entity is clearly identified by its own attributes.
    • Cons: Business keys can be difficult to find (ensuring true uniqueness) and are risky if the key is not actually stable; if a business key changes, updating it as a primary key is difficult.
  11. Configure Node Labels with @Node

    main

    The @Node annotation marks a class as a managed domain class. It uses the labels attribute (or its alias value) to define the Neo4j labels used for reading and writing.

    • Default Behavior: If no label is specified, the simple class name is used as the primary label.
    • Multiple Labels: You can provide an array of labels to the labels property. The first element is treated as the primary label.
    • Primary Label: This should be the most concrete label reflecting the domain class. SDN writes at least the primary label to the graph, and all nodes with that primary label are mapped back to this class.

    Class Hierarchies and Polymorphism

    • Inheritance: @Node is not inherited from super-types or interfaces. You must annotate every level of the hierarchy if you want polymorphic queries.
    • Abstract Bases: Annotating an abstract base class with @Node allows its labels to be used as additional labels alongside the concrete implementation's labels.
    • Interfaces: To support interfaces in a domain model, you must annotate the interface with @Node and ensure the implementing class uses the exact same primary label name to synchronize them.
    @Node("SomeInterface")
    public interface SomeInterface {
        String getName();
        SomeInterface getRelated();
    }
    
    @Node("SomeInterface") // Must match the interface label
    public class SomeInterfaceEntity implements SomeInterface {
        @Id
        @GeneratedValue
        private Long id;
        // ...
    }