22. Cross-store persistence

The Spring Data Neo4j project support cross-store persistence for the advanced mapping mode, which allows for parts of the data to be stored in a traditional JPA data store (RDBMS), and other parts in a graph store. This means that an entity can be partially stored in e.g. MySQL, and partially stored in Neo4j.

This allows existing JPA-based applications to embrace NOSQL data stores for evolving certain parts of their data model. Possible use cases include adding social networking or geospatial information to existing applications.

22.1 Partial entities

Partial graph persistence is achieved by restricting the Spring Data Neo4j aspects to manage only explicitly annotated parts of the entity. Those fields will be made @Transient by the aspect so that JPA ignores them.

A backing node in the graph store is only created when the entity has been assigned a JPA ID. Only then will the association between the two stores be established. Until the entity has been persisted, its state is just kept inside the POJO (in detached state), and then flushed to the backing graph database on the persist operation.

The association between the two entities is maintained via a FOREIGN_ID field in the node, that contains the JPA ID. Currently only single-value IDs are supported. The entity class can be resolved via the TypeRepresentationStrategy that manages the Java type hierarchy within the graph database. Given the ID and class, you can then retrieve the appropriate JPA entity for a given node.

The other direction is handled by indexing the Node with the FOREIGN_ID index which contains a concatenation of the fully qualified class name of the JPA entity and the ID. The matching node can then be found using the indexing facilities, and the two entities can be reassociated.

Using these mechanisms and the Spring Data Neo4j aspects, a single POJO can contain some fields handled by JPA and others handles by Spring Data Neo4j. This also includes relationship fields persisted in the graph database.

22.2 Cross-store annotations

Cross-store persistence only requires the use of one additional annotation: @GraphProperty. See below for details and an example.

22.2.1 @NodeEntity(partial = "true")

When annotating an entity with partial = true, this marks it as a cross-store entity. Spring Data Neo4j will thus only manage fields explicitly annotated with @GraphProperty.

22.2.2 @GraphProperty

Fields of primitive or convertible types do not normally have to be annotated in order to be persisted by Spring Data Neo4j. In cross-store mode, Spring Data Neo4j only persists fields explicitly annotated with @GraphProperty. JPA will ignore these fields.

22.2.3 Example

The following example is taken from the Spring Data Neo4j examples myrestaurants-social project:

Example 22.1. Cross-store node entity

@Entity
@Table(name = "user_account")
@NodeEntity(partial = true)
public class UserAccount {
    private String userName;
    private String firstName;
    private String lastName;

    @GraphProperty
    String nickname;

    @RelatedTo
    Set<UserAccount> friends;

    @RelatedToVia(type = "recommends")
    Iterable<Recommendation> recommendations;

    @Temporal(TemporalType.TIMESTAMP)
    @DateTimeFormat(style = "S-")
    private Date birthDate;

    @ManyToMany(cascade = CascadeType.ALL)
    private Set<Restaurant> favorites;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    public void knows(UserAccount friend) {
        relateTo(friend, "friends");
    }

    public Recommendation rate(Restaurant restaurant, int stars, String comment) {
        Recommendation recommendation = relateTo(restaurant, Recommendation.class, "recommends");
        recommendation.rate(stars, comment);
        return recommendation;
    }

    public Iterable<Recommendation> getRecommendations() {
        return recommendations;
    }
}


22.3 Configuring cross-store persistence

Configuring cross-store persistence is done similarly to the default Spring Data Neo4j configuration. All you need to do is to specify an entityManagerFactory in the XML namespace config element, and Spring Data Neo4j will configure itself for cross-store use.

Example 22.2. Cross-store Spring configuration

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:datagraph="http://www.springframework.org/schema/data/neo4j"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/data/neo4j
        http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd
        ">

    <context:annotation-config/>

    <neo4j:config storeDirectory="target/config-test"
        entityManagerFactory="entityManagerFactory"/>

    <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
            id="entityManagerFactory">
        <property name="dataSource" ref="dataSource"/>
        <property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml"/>
    </bean>
</beans>