Friday, July 1, 2016

[DevNote] Morphia Example with Spring Framework

full code example is available on GitHub

This is part of my note on Morphia with Spring MVC. I will try to demonstrate in this blog how to setup Morphia with Spring MVC Framework.

There are multiple flavors when it comes to ODM (object document mapper) for MongoDB, there's Jongo, Morphia, Spring Data, Hibernate OGM and etc,. Different applications may have different needs. I've tried with some personally and I feel like Morphia and Spring Data are more mature and relatively easy to use.

Particularly we are looking at Morphia in this blog. We are going to use Spring as our application framework, Morphia as our ODM and DB access layer, JUnit as our testing tool to verify configuration and test our DB access layer, and we are going to use a little bit of Spring Data also to perform drop_collection actions.

1. Setting up your project.


We are using Maven as our project management tool and to use Morphia and Spring MVC you will need some dependencies added for them.

Spring property:

1
2
3
4
5
6
7
8
9
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.3.5.RELEASE</version>
 </parent>

 <properties>
  <java.version>1.8</java.version>
 </properties>

Dependency for Morphia:

1
2
3
4
5
  <dependency>
   <groupId>org.mongodb.morphia</groupId>
   <artifactId>morphia</artifactId>
   <version>1.0.0-rc0</version>
  </dependency>

Dependency for JUnit:

1
2
3
4
5
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
  </dependency>

Dependency for Spring Data:

1
2
3
4
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-mongodb</artifactId>
  </dependency>

These dependencies are required for our example application and there's nothing special to explain really.


2. Configuration


There are two ways of configuring a Spring application. One is via XML property file, while the other is through Java annotation.

I prefer Java annotation as the configuration since it's more type-safe, typo-safe compared to XML property file. One can capture any typo occurred or any unsuitable types immediately.

Here's the configuration of our application.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Configuration
public class DatabaseConfiguration {

 private static final String host = "127.0.0.1";
 private static final String port = "27017";
 private static final String dbname = "morphiaTest";
 
 public @Bean MongoClient mongoClient() throws UnknownHostException {
  return (new MongoClient(host+":"+port));
 }
 
 @Bean
 public Datastore datastore() throws UnknownHostException {
  Morphia morphia = new Morphia();
  morphia.mapPackage("com.hackinghorse.morphiamongoexample.model");
        Datastore datastore = morphia.createDatastore(mongoClient(), dbname);
        datastore.ensureIndexes();
        return datastore;
 }
 
 public @Bean MongoDbFactory mongoDbFactory() throws UnknownHostException {
  return new SimpleMongoDbFactory(mongoClient(), dbname);
 }

 public @Bean MongoTemplate mongoTemplate() throws UnknownHostException {
  MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory());
  return mongoTemplate;
 }
}

@Configuration annotation is provided by Spring and this tells Spring to process this class since it will contain multiple @Bean methods which will be added to the Spring container. This is not necessary for this project since we can specify our configuration in JUnit test class, this I will demonstrate later.

This configuration class defines multiple beans we are going to use. @Bean annotation tells Spring that a method annotated with @Bean will produce a bean which needs to be managed by Spring container.

A Spring bean is actually an Singleton object managed by spring container. We can retrieve the bean through ApplicationContext interface or through @Autowired annotation which will search and match the bean according to their name and type.

Note that inside this function a @Bean method is calling another @Bean method within the same class, for example we are calling mongoClient() method inside datastore() method. Spring ensures that references between beans are strongly typed and navigable and when a @Bean method is being called/referenced the @Bean method being called/referenced is guaranteed to return the same bean as it would for other places.

Also note that if we are intended to reference @Bean methods among each other within the same class, the @Configuration class and its factory methods should not be final or private.

The datastore() bean method is going to create a Morphia Datastore object. The Datastore object will provide all the interfaces we need to access, manipulate the MongoDB.

mongoTemplate() bean method is required for Spring Data. The reason to add this is that there seems to be no API in Morphia that can drop the whole collection, so I used this MongoTemplate as a shortcut of cleaning up our datastore after each run of our application. MongoTemplate provides remove() interface which will provide drop collection capability.


3. Model


Data model is usually POJO classes with annotation provided by the ODM vendor. In our case we are using Morphia annotations for data model.

Java code for data model of Customer.java:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Entity(value="customers")
public class Customer {
 
 @Id
 private ObjectId id;
 
 private String firstName;
 private String lastName;
 private String created;
 
 @Reference
 private Group group;

       //--------getter/setter---------
}

The setters and getters are not shown, they are just boilerplate code.

@Entity annotation is going to tell Morphia that this class is a model for a Mongo document. The value specified in the annotation defines the name of the collection.

@Id annotation specifies the id member field in the model, this will be automatically populated by Morphia once saved into database. By default it is using the default mongo _id format.

@Reference here models a one-to-one relationship between another model Group. In stead of going with embedded, we choose to use normalized data which uses reference. (There's no specific reason for using this to model them, just what to demonstrate @Reference)

We have another data model Group.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Entity(value = "groups")
public class Group {

 @Id
 private ObjectId id;

 @Indexed(value = IndexDirection.ASC, unique = true, dropDups = true)
 private String groupId;

 private String groupName;

 @Reference
 List<Customer> customers;

       //------- setters/getters ------
}


In Group.java, we have a one-to-many relationship with @Reference annotated. There's also an @Indexed annotation which models a indexed field. By default MongoDB indexes on _id field and user can opt to use any field as the index. Here we are using groupId as our index field and we order them by ascending order with "value=IndexDirection.ASC" and make sure they are unique with "unique=true". We also opt to drop duplicates, so keep in mind that when we do save() with Datastore API we can get an DuplicateKeysException.

4. Test

(Will post later...)

1 comment: