The Spring Data Graph project support cross-store persistence, 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.
Partial graph persistence is achieved by restricting the Spring Data Graph 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 persist()
.
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 Graph aspects, a single POJO can contain some fields handled by JPA and others handles by Spring Data Graph. This also includes relationship fields persisted in the graph database.
Cross-store persistence only requires the use of one additional annotation: @GraphProperty
.
See below for details and an example.
When annotating an entity with partial = true
, this marks it as a cross-store entity.
Spring Data Graph will thus only manage fields explicitly annotated with @GraphProperty
.
Fields of primitive or convertible types do not normally have to be annotated in order to be
persisted by Spring Data Graph. In cross-store mode, Spring Data Graph only
persists fields explicitly annotated with @GraphProperty
. JPA will ignore these fields.
The following example is taken from the Spring Data Graph examples myrestaurants-social project:
Example 21.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(type = "friends", elementClass = UserAccount.class) Set<UserAccount> friends; @RelatedToVia(type = "recommends", elementClass = Recommendation.class) 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; @Transactional public void knows(UserAccount friend) { relateTo(friend, "friends"); } @Transactional 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; } }
Configuring cross-store persistence is done similarly to the default Spring Data Graph configuration.
All you need to do is to specify an entityManagerFactory
in the XML namespace
config
element, and Spring Data Graph will configure itself for cross-store use.
Example 21.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/graph" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/data/graph http://www.springframework.org/schema/data/graph/datagraph-1.0.xsd "> <context:annotation-config/> <datagraph: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>