Using MongoDB with Morphia
Morphia - The JVM Object Document Mapper for MongoDB.
MongoDB is an extremely useful document-based database in NoSQL field. There are various drivers and client libraries in MongoDB Ecosystem and detailed manual to facilitate the developers to get into it shortly. In the beginning, we use MongoDB Java driver to manipulate CRUD operations on databases. Everything is running well but lots of BasicDBObject
cause the code lack of readability and fluency. In addition, we employ POJO classes as intermediates between business logic and databases. However, it also brings many laborious and tedious works for converting POJOs into BasicDBObjects before we want to persist custom objects to the database.
Then we find the Java driver provides a DBObject
interface to save custom objects to the database as long as the POJOs implement this interface:
public class CustomClass implements DBObject {
/* ... */
}
This approach offers an easy way to convert the document we retrieved to a DBobject instance. Then we can perform some operations (e.g, set values to fields) and save it back easily:
collection.save(CustomClass);
However, is there a better way to do this?
Morphia
In my opinion, Morphia is a better alternative library for mapping POJOs to/from MongoDB. We can define our entity by Annotations, including the embedded and reference documents within the database. Most of all, Morphia also provides the Data Access Objects (DAO) mechanism to persist the objects. Here’s an excellent introduction to Morphia provided by Jeff Yemin.
The following part of this article will note down some usage or discussions of Morphia.
MongoClient
We use MongoClient to create a database connection with internal connection pool instead of deprecated Mongo class. It’s designed with the Singleton pattern, and the properties are defined as resource in order to easy to modify. Then just call the getMongoClient
function to get a MongoClient instance.
package com.example.morphia;
import java.net.UnknownHostException;
import java.util.ResourceBundle;
import com.mongodb.MongoClient;
public class MongoConnectionHelper {
private static MongoClient mongoSingleton = null;
public static synchronized MongoClient getMongoClient() throws UnknownHostException {
if (mongoSingleton == null) {
synchronized (MongoConnectionHelper.class) {
if (mongoSingleton == null) {
ResourceBundle bundle = ResourceBundle.getBundle("mongodb");
String host = bundle.getString("host");
int port = Integer.parseInt(bundle.getString("port"));
mongoSingleton = new MongoClient(host, port);
}
}
}
return mongoSingleton;
}
}
Serialization and Deserialization ObjectId with Jackson
In order to serve an object in JSON as a RESTful resource, we can turn on the JSON POJO support in web.xml
in Tomcat:
...
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
...
However, we will get something like this formation for ObjectId field in returned JSON,
{
"id": {
"machine": 615264403,
"timeSecond": 1376632233,
"inc": -1018127256,
"new": false,
"time": 1376632233000
}
}
instead of the following style:
"id": "520dbda924ac3093c3509c68"
Fortunately, Alejandro Ayuso provides a way to solve the Jackson serialization problem for MongoDB’s ObjectId - the ObjectIdSerializer
class and your custom class can set JsonSerialize
property with it:
@Entity
public class MyEntity {
@JsonSerialize(using = ObjectIdSerializer.class)
@Id
ObjectId id;
...
}
Performance Issue
Scalabiliti conducts an experiment to evaluate the performance between Morphia and MongoDB with Java driver. According to the results, Morphia has slightly difference in the test.
With a few code changes for optimisation, Morphia has now made it to within 99.5% of the performance of the base Mongo install.
Array Operatios
Morphia provides a fluent and comfortable interface to operate arrays. We can remove one or multi objects from an array field through removeAll
function.
Datastore ds = ...
Query<MyEntity> query = ds.find(MyEntity.class, field, value);
UpdateOperations<MyEntity> ops = ds.createUpdateOperations(
MyEntity.class).removeAll(field, value);
UpdateResults<MyEntity> results = ds.update(query, ops);
Moreover, you can pull out more than one object by providing List<?>
values.
List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
UpdateOperations<MyEntity> ops = ds.createUpdateOperations(
MyEntity.class).removeAll(field, stooges);