Sometimes you need to store data in multiple data stores and these data stores can be of different types. One might be relational while the other a document store. For this use case we have created a separate module in the MongoDB support that handles what we call cross-store support. The current implemenatation is based on JPA as the driver for the relational database and we allow select fields in the Entities to be stored in a Mongo database. In addition to allowing you to store your data in two stores we also coordinate persistence operations for the non-transactional MongoDB store with the transaction life-cycle for the relational database.
Assuming that you have a working JPA application and would like to add some cross-store persistence for MongoDB. What do you have to add to your configuration?
First of all you need to add a dependency on the
spring-data-mongodb-cross-store
module. Using Maven
this is done by adding a dependency to your pom:
Example 8.1. Example Maven pom.xml with spring-data-mongodb-cross-store dependency
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> ... <!-- Spring Data --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-mongodb-cross-store</artifactId> <version>${spring.data.mongo.version}</version> </dependency> ... </project>
Once this is done we need to enable AspectJ for the project. The cross-store support is implemented using AspectJ aspects so by enabling compile time AspectJ support the cross-store features will become available to your project. In Maven you would add an additional plugin to the <build> section of the pom:
Example 8.2. Example Maven pom.xml with AspectJ plugin enabled
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> ... <build> <plugins> ... <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.0</version> <dependencies> <!-- NB: You must use Maven 2.0.9 or above or these are ignored (see MNG-2972) --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>${aspectj.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjtools</artifactId> <version>${aspectj.version}</version> </dependency> </dependencies> <executions> <execution> <goals> <goal>compile</goal> <goal>test-compile</goal> </goals> </execution> </executions> <configuration> <outxml>true</outxml> <aspectLibraries> <aspectLibrary> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </aspectLibrary> <aspectLibrary> <groupId>org.springframework.data</groupId> <artifactId>spring-data-mongodb-cross-store</artifactId> </aspectLibrary> </aspectLibraries> <source>1.6</source> <target>1.6</target> </configuration> </plugin> ... </plugins> </build> ... </project>
Finally, you need to configure your project to use MongoDB and also configure the aspects that are used. The following XML snippet should be added to your application context:
Example 8.3. Example application context with MongoDB and cross-store aspect support
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:mongo="http://www.springframework.org/schema/data/mongo" xsi:schemaLocation="http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd"> ... <!-- Mongo config --> <mongo:mongo host="localhost" port="27017"/> <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate"> <constructor-arg name="mongo" ref="mongo"/> <constructor-arg name="databaseName" value="test"/> <constructor-arg name="defaultCollectionName" value="cross-store"/> </bean> <bean class="org.springframework.data.mongodb.core.MongoExceptionTranslator"/> <!-- Mongo cross-store aspect config --> <bean class="org.springframework.data.persistence.document.mongo.MongoDocumentBacking" factory-method="aspectOf"> <property name="changeSetPersister" ref="mongoChangeSetPersister"/> </bean> <bean id="mongoChangeSetPersister" class="org.springframework.data.persistence.document.mongo.MongoChangeSetPersister"> <property name="mongoTemplate" ref="mongoTemplate"/> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> ... </beans>
We are assuming that you have a working JPA application so we will
only cover the additional steps needed to persist part of your Entity in
your Mongo database. First you need to identify the field you want
persited. It should be a domain class and follow the general rules for the
Mongo mapping support covered in previous chapters. The field you want
persisted in MongoDB should be annotated using the
@RelatedDocument
annotation. That is really all you
need to do!. The cross-store aspects take care of the rest. This includes
marking the field with @Transient so it won't be persisted using JPA,
keeping track of any changes made to the field value and writing them to
the database on succesfull transaction completion, loading the document
from MongoDB the first time the value is used in your application. Here is
an example of a simple Entity that has a field annotated with
@RelatedEntity.
Example 8.4. Example of Entity with @RelatedDocument
@Entity public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String firstName; private String lastName; @RelatedDocument private SurveyInfo surveyInfo; // getters and setters omitted }
Example 8.5. Example of domain class to be stored as document
public class SurveyInfo { private Map<String, String> questionsAndAnswers; public SurveyInfo() { this.questionsAndAnswers = new HashMap<String, String>(); } public SurveyInfo(Map<String, String> questionsAndAnswers) { this.questionsAndAnswers = questionsAndAnswers; } public Map<String, String> getQuestionsAndAnswers() { return questionsAndAnswers; } public void setQuestionsAndAnswers(Map<String, String> questionsAndAnswers) { this.questionsAndAnswers = questionsAndAnswers; } public SurveyInfo addQuestionAndAnswer(String question, String answer) { this.questionsAndAnswers.put(question, answer); return this; } }
Once the SurveyInfo has been set on the Customer object above the MongoTemplate that was configured above is used to save the SurveyInfo along with some metadata about the JPA Entity is stored in a MongoDB collection named after the fully qualified name of the JPA Entity class. The following code:
Example 8.6. Example of code using the JPA Entity configured for cross-store persistence
Customer customer = new Customer(); customer.setFirstName("Sven"); customer.setLastName("Olafsen"); SurveyInfo surveyInfo = new SurveyInfo() .addQuestionAndAnswer("age", "22") .addQuestionAndAnswer("married", "Yes") .addQuestionAndAnswer("citizenship", "Norwegian"); customer.setSurveyInfo(surveyInfo); customerRepository.save(customer);
Executing the code above results in the following JSON document stored in MongoDB.
Example 8.7. Example of JSON document stored in MongoDB
{ "_id" : ObjectId( "4d9e8b6e3c55287f87d4b79e" ), "_entity_id" : 1, "_entity_class" : "org.springframework.data.mongodb.examples.custsvc.domain.Customer", "_entity_field_name" : "surveyInfo", "questionsAndAnswers" : { "married" : "Yes", "age" : "22", "citizenship" : "Norwegian" }, "_entity_field_class" : "org.springframework.data.mongodb.examples.custsvc.domain.SurveyInfo" }