Jongo – The Java Object Document Mapper for MongoDB

This tutorial explains about the Jongo, a Java query language for accessing the data from MongoDB database. When you work with the MongoDB database, you have to use the Mongo shell for querying the data, however when you are querying in the Java application, you have to use the Java driver for MongoDB and retrieve the data. This approach is not easy and needs complex syntax. Jongo addresses the problem by Query in Java as in Mongo shell.

MongoDB is an open-source noSQL database written in C++ that provides high performance, high availability and auto-scaling. A record in a MongoDB is called a Document. A document is a data structure composed of keys and corresponding values. MongoDB documents are similar to JSON objects as shown:

MongoDB record

MongoDB document

Java MongoDB Driver

MongoDB provides a Java driver to communicate with MongoDB database. Please follow  ttp://docs.mongodb.org/ecosystem/drivers/java for more details on how to connect to a MongoDB server via Java application.

DBObject API

The Java driver for MongoDB uses DBObject interface and its subclasses for persisting and representing documents in the database. This API provides bare bones methods to persist and retrieve Java objects into MongoDB database.

Below is the class diagram of DBObject API

DBObjectAPI
Lets use the DBObject to persist a Java POJO into MongoDB.

Person.java

public class Person {
    private String _id;
    private String firstName;
    private String lastName;
    private Address address;
   //getters & setters
}

Address.java

public class Address {
    private String houseNo;
    private String street;
    private String city;
    private String country;
    private String zip;
}

There are two POJOs Address and Person and Address is referenced inside Person. We’ll first try to insert an instance of the Person as Mongodb Document. Also, let’s create a Mongo Persistence DAO and its corresponding implementation.

MongoService.java

public interface MongoService<T> {
    Object save (T t);
    T get(String key);
    List<T> getAll();
    void dropCollection(String collectionName);
}

MongoServiceImpl.java

public class MongoServiceImpl implements MongoService<Person> {
    protected String connectionURL = "localhost:27017";
    public static final String collectionName = "persons";
    protected DBCollectiom collection;
    protcted DB;

    public MongoService() {
        db = MongoConnectionManager.connect(connectionURL);
        collection = db.getCollection(collectionName);
   }
   //....
}

Below is the implementation of save() method:

public Object save(Person p) {
    //first extract Address from person and build the corresponding BasicDBObject
    BasicDBObject addrObj = new BasicDBObject();
    Address addr = p.getAddress();
    addrObj.append("houseNo", addr.getHouseNo());
    addrObj.append("street", addr.getStreet());
    addrObj.append("city", addr.getCity());
    //....
    //Now build the BasicDBObject for Person
    BasicDBObject personObj = new BasicDBObject();
    personObj.append("firstName", t.getFirstName());
    personObj.append("lastName", t.getLastName());
    personObj.append("address", addrObj); //Note how addrObj is mapped here

    //finally save the personObj to the MongoDB database
    collection.insert(personObj);
    return personObj.get("_id");
}

To save a Person instance having an embedded Address object we have to create two BasicDBObject instances and use hard coded keys to populate them. A call to insert(DBObject o) method of DBCollection class to store the person instance in the database. Finally, we simply retrieve the persisted document id using the get(String key) method of BasicDBObject and passed "_id" to retrieve it. The retrieved id can be used to query the persisted object.

Let’s retrieve the persisted MongoDB document from the database by querying the peristed id.

public Person get(String id) {
    BasicDBObject persistent = (BasicDBObject)collection.findOne(new ObjectId(id));
    //The retrieved object is a BasicDBObject. Extract values from this object and
    //build a person object.
    Person person = new Person();
    Set<String> keySet = persistent.keySet();
    for(String key: keySet) {
        Object value = persistent.get(key);
        buildPerson(key, value, person);
    }
    return person;
}

Limitations

Its evident from the above example that saving and retrieving a document using the DBObject API is very daunting. We can easily lose track of POJO attributes that serves as keys to the MongoDB document. For any project that has a number of POJOs, saving and retrieving objects using DBObject API will be very frustrating. Java Mongo Driver is a poor man’s tool for performing database operations.

Using JSON Jackson along with Mongo Java Driver – The Jongo Way

Using bare bone DBObject API can be frustrating. However, a combination of JSON Jackson and Mongo Java Driver can ease the storage and retrieval of POJOs. Moreover, the embedded POJOs don’t have to be dealt separately while persisting and retrieval. The following piece of code makes use of byte[] to marshall and unmarshall Java objects.

public Object save(Object o) throws IOException {
   ObjectWriter writer = mapper.writer();
   ByteArrayOutputStream baos = new ByteArrayOutputStream();
   writer.writeValue(baos, o);
   DBObject dbObject = new LazyWriteableDBObject(baos.toByteArray(), new LazyBSONCallback());
   DBObject result = new BasicDBObject();
   result.putAll(dbObject);
   collection.insert(result);
   return result.get("_id");
}

The save(Object o) method use ByteArrayOutputStream to create a DBObject. The DBObject is then persisted. In this case, we don’t have to create separate DBObject instances for Container POJO and embedded POJO. The complete object graph is first converted to ByteArrayOutputStream and then persisted.

public static <T> T getPojo(DBObject o, Class<T> clazz) throws IOException {
    ObjectReader reader = mapper.reader(clazz);
    DBEncoder dbEncoder = DefaultDBEncoder.FACTORY.create();
    OutputBuffer buffer = new BasicOutputBuffer();
    dbEncoder.writeObject(buffer, o);
    T pojo = reader.readValue(buffer.toByteArray());
    return pojo;
}

The getPojo() method converts a given DBObject to the required class type. The JSON Jackson ObjectReader converts a given DBObject to the POJO type.

Jongo makes use of the Jackson JSON API and Mongo Driver. Instead of converting results into strings and unmarshalling strings into objects, Jongo use byte[] for query marshalling and unmarshalling. Jongo also provides Aggregation f/w to work with MongoDB Aggregations. More on Jongo…

Comments

comments

Speak Your Mind

*