The Spring Data Graph project support cross-store persistence which allows parts of the data mode to be stored in a traditional JPA datastore (RDBMS) and other parts of the data model (even partial entites, that is some properties or relationships) in a graph store.
This allows existing JPA-based applications to embrace NOSQL data stores to evolve certain parts of their model. Possible use cases are adding social network or geospatial information to existing applications.
Partial graph persistence is achieved by restricting the Spring Data Graph aspects to explicitly annotated parts of the entity. Those fields will be made transient by the aspect so that JPA ignores them and won't try to persist those attributes.
A backing node in the graph store is only created when the entity has been assigned a JPA id. Only then will the connection between the two stores be kept. Until the entity has been persisted, its state is just kept inside the POJO (detached state) and flushed to the backing graph store afterwards.
The connection between the two entities is kept 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 NodeTypeStrategy that preserves the Java type hierarchy within the graph. With 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. So it is possible on instantiation of a JPA id via the entity manager (or some other means like creating the POJO and setting its id manually) to find the matching node using the index facilities and reconnect them.
Using those mechanisms and the Spring Data Graph aspects a single POJO can contain fields that are handled by JPA and other fields (which might be relationships as well) that are handled by Spring Data Graph.
When annotating an entity with partial true, Spring Data Graph assumes that this is a cross-store entity. So its only responsibility is for the fields annotated with Spring Data Graph annotations. JPA should not take care of these fields (they should be annotated with @Transient). In this mode of operation Spring Data Graph also handles the cross-store connection via the content of the JPA id field.
For common fields containing primitive or convertible values that wouldn't have to be annotated in exclusive Spring Data Graph operations this explicit declaration is necessary to be sure that they are intended to be stored in the graph. These fields should then be made transient so that JPA doesn't try to take care of them as well.
The following example is taken from the Spring Data Graph examples, it is contained in the myrestaurant-social project.
@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 operations. As soon as you refer
to an entityManagerFactory
in the xml-namespace it is set up for cross-store persistence.
<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>