NoSQL Zone is brought to you in partnership with:

I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 638 posts at DZone. You can read more from them at their website. View Full User Profile

MongoDB and Java

04.30.2012
| 18837 views |
  • submit to reddit
So you want a NoSQL database, and you have chosen MongoDB. Here is a tutorial on how easy MongoDB can be integrated with Java application and some advice on how to isolate your code from the database as much as possible, for encapsulation purposes.

The API

Many aspects of the MongoDB API are not language specific, like the terms and the formats used for communicating with the persistence mechanism (JSON on the client side and BSON for transport and storage).

You can download a single JAR containing the Java bindings of the API, where the com.mongodb package is the main one that you will import classes from.

The setup is pretty simple: a hierarchy of Mongo (connection), DB and Collection objects, each contained into the other. A Collection loosely correspond to a relational table, but since embedded objects are allowed and arbitrary fields can be added to contained objects, it is more similar to an Aggregate class.

Mongo mongo = new Mongo("127.0.0.1");
DB db = mongo.getDB("test_database");
DBCollection collection = db.getCollection("test");

You can insert in a collection instances of DBObject (usually BasicDBObject), which can be created from a JSON string or programmatically:

DBObject article = new BasicDBObject();
article.put("title", "MongoDB and Java");
collection.insert(article);

Querying also with a DBObject that resemble in structure what you want (only valorize fields if you want to make a match on their value):

DBObject query = new BasicDBObject();
DBObject result = collection.findOne(query);

Special string keys (starting with $) allow for matching not based on equality. Check out MongoDB's Java tutorial for more about queries and indexes.

Isolation

I don't want com.mongodb.DBObject hanging around in arbitrary places in my applications, which in most places has nothing to do with MongoDB and persistence: this persistence agnosticism tells me to try hiding the database from most of my code.

Even if DBObjects and their implementations are the format that must be used to talk with MongoDB, we are not forced to use them inside the application. We can:

  • Convert JSON objects (instances of a class of yours) to DBObjects for storage.
  • Convert DBObjects returned after querying into JSON objects.

JSON objects can be just JSON strings (a rare choice), a JSON manipulable representation (there are plenty of Java libraries for that), or domain objects. The com.mongodb.util.JSON class has two static methods for going back and forth between a String and DBObject, so everything that you can serialize to a JSON string can be used inside the application. The impedance mismatch between object aggregates and MongoDB's model is minimal.

The natural choice for hiding the translation between domain and Mongo-based format is the Repository pattern, which in fact models a Collection of objects. If the conversion is really complex, a Data Mapper like Morphia can be used internally.

But for starting out, producing a JSON string representation of an object manually may be enough. In any case, the persistence strategy decision is hidden inside the particular Repository implementation, so you'll be able to change it later if the code grows.

Writing tests

There is no Fake equivalent for the Mongo connection object: Sqlite can be used as a fast, in-memory, non-transactional test database for relational solutions, but there aren't alternate implementations of MongoDB yet.

So when tests involve persistence, we must use the real one. The mongod service must be started separately (usually as a Unix daemon or a Windows service). On Linux platforms, check that the configuration in mongodb.conf defines a dbpath option as a folder where the mongodb user can write.

At the end of each test, resetting the database is necessary. In end to end tests, and in the tests of the Repository itself, you will then use a brand new instance of the database. There is no schema to create, nor constraints to be satisfied: it's very simple as even if the database do not exist MongoDB will create an emtpy one for you.
Here is an example, the test for my Repository containing JSONObject instances (representing User's here):

package it.polimi.cubrikusers.persistence;

import static org.junit.Assert.*;

import java.net.UnknownHostException;

import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;

import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.Mongo;
import com.mongodb.MongoException;

public class MongoUserRepositoryTest {
    DB db;
    MongoUserRepository repository;
    
    @Before
    public void setUp() throws UnknownHostException, MongoException
    {
        Mongo connection = new Mongo("127.0.0.1");
        db = connection.getDB("test");
        DBCollection collection = db.getCollection("users");
        repository = new MongoUserRepository(collection);
    }
    @Test
    public void testCanStoreAUserJSONObjectAndRetrieveIt() {
        JSONObject user = new JSONObject()
            .put("id", 1)
            .put("membership", new JSONObject()
                .put("username", "giorgio"));
        repository.add(user);
        JSONObject retrieved = repository.findBy("id", 1);
        assertEquals(user.getJSONObject("membership").getString("username"),
                     retrieved.getJSONObject("membership").getString("username"));
    }
    
    public void tearDown() {
        db.dropDatabase();
    }

}

Here's the Repository implementation, if you're curious:

package it.polimi.cubrikusers.persistence;

import org.json.JSONObject;

import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;

public class MongoUserRepository {

    private DBCollection collection;

    public MongoUserRepository(DBCollection collection) {
        this.collection = collection;
    }
    
    public void add(JSONObject user) {
        DBObject object = (DBObject) JSON.parse(user.toString());
        collection.insert(object);
    }

    public JSONObject findBy(String field, Object value) {
        BasicDBObject query = new BasicDBObject();
        query.put(field, value);
        DBObject found = collection.findOne(query);
        return new JSONObject(JSON.serialize(found));
    }
}

I am using the JSONObject and related classes from json.org.

Of course in most of the unit tests, you will be able to stub the Repository, and to avoid touching MongoDB altogether for greater speed and simplicity.

Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)