Jongo

Query in Java as in Mongo shell
Shell
db.friends.find({age: {$gt: 18}})
Java Driver
friends.find(new BasicDBObject("age",new BasicDBObject("$gt",18)))
Jongo
friends.find("{age: {$gt: 18}}").as(Friend.class)

Faithful spiritMongo query language isn't available in Java, Jongo fixes that. Copy/paste your queries to string.

Object orientedSave & find objects into & from collections. Use embedded Jackson marshalling or your own.

Wood solidAs fast as Mongo Java driver. Open source, fully tested & made of rock solid librairies.

Set up

First install Mongo, then choose a driver's version, finally download Jongo and you're all set.

DB db = new MongoClient().getDB("dbname");

Jongo jongo = new Jongo(db);
MongoCollection friends = jongo.getCollection("friends");

Iterable<Friend> all = friends.find("{name: 'Joe'}").as(Friend.class);
Friend one = friends.findOne("{name: 'Joe'}").as(Friend.class);

Updating

Save

Passing an object to the save(..) method will do the job. See mapping section for objects defintion.

Friend joe = new Friend("Joe", 27);

friends.save(joe);
joe.age = 28;
friends.save(joe);

Update

Update syntax is a bit different than in Mongo shell: modifier query has to be passed using with(..). You can update with a String or with an object. You can also parameterize queries with # (using primitives or complex objects), see query templating for more details.

friends.update("{name: 'Joe'}").with("{$inc: {age: 1}}");
friends.update("{name: 'Joe'}").upsert().multi().with("{$inc: {age: 1}}");

friends.update("{name: 'Joe'}").with(new Friend(..));
friends.update("{name: 'Joe'}").with("{$set: {address: #}}", new Address(..));

Insert

Insert works as in Mongo shell.

friends.insert("{name: 'Joe', age: 18}");

friends.insert(new Friend(..));
friends.insert(new Friend(..), new Friend(..));

Remove

Remove works as in Mongo shell.

friends.remove("{name: 'Joe'}");
friends.remove(new ObjectId("4c...e"));

Object Mapping

Query results are automatically mapped to objects. By default, this relies on Jackson; it respects document structure, handles lists and ignores missing attributes. It just need a no-arg constructor, even private (if the object has to be immutable, @JsonCreator annotation can be use instead).

_id is a unique identifier available on every Mongo document. If it isn't setted, it is generated. To handle it with Jongo, one attribute has to be named _id or annotated with @Id (alias for @JsonProperty("_id")). It can be handled with the dedicated ObjectId class or with a simple String — annotated @ObjectId.

Careful, saving a document with a custom _id (ie: any Java type, other than array, so long as it is unique) always involves to set it manually before persisting.

public class Friend {
    // manual
    private long _id;
}
public class Friend {
    @Id // manual
    private long key;
}
public class Friend {
    @Id // manual
    private String key;
}
public class Friend {
    @ObjectId // auto
    private String _id;
}
public class Friend {
    @Id @ObjectId // auto
    private String key;
}
public class Friend {
    // auto
    private ObjectId _id;
}

JSON properties are automatically mapped to their equivalent object attributes (and complex ones mapped as sub-attributes). No extra annotations needed. To ignore a pojo attribute, annotate it with @JsonIgnore.
For fine grained control over (un)marshalling, configure Jongo mapper or create your own.

Handling polymorphism

To handle polymorphism of Java objects, a field containing the class name must be set into the Mongo document. One can persist a Fox (sub-class of Friend) and manipulate it as a Friend. This feature is provided out-of-the-box by Jackson. Using another (un)marshaller implies extra work.

@JsonTypeInfo(use=Id.CLASS,property="_class")
public class Friend {
    int age;
}
public class Fox extends Friend {
    String name, color;
}
friends.save(new Fox("fantastic", "red-haired"));
Friend friend = friends.findOne("{name:'fantastic'}").as(Friend.class);
Fox fantastic = (Fox)friend;

Using a singular reserved name, like _class, for this field is a good idea.

Querying

Find & FindOne

Query syntax is almost the same as in Mongo shell: copy/paste, it just works.
Strings have to be escaped with single quotes "{name: 'Joe'}", numbers don't "{age: 18}".

Iterable<Friend> all = friends.find("{name: 'Joe'}").as(Friend.class);

Friend joe = friends.findOne("{name: 'Joe'}").as(Friend.class);
Friend joe = friends.findOne(new ObjectId("4c...e")).as(Friend.class);

Field names need quotes when using dot notation "{'address.city': 'London'}".

Projection or Field selection

Field selection aka. partial loading is not written as in Mongo shell: Jongo exposes a projection(..) method. A json selector must be provided: {field: 1} to include it, {field: 0} to exclude it.

friends.find().projection("{lastname: 1, address: 1}").as(Friend.class);

Sorting

Documents can be sorted in ascending or descending order using a json selector: {field: 1} for ascending, {field: -1} for descending.

friends.find().sort("{firstname: 1}").as(Friend.class);
friends.find().sort("{lasttname: -1}").as(Friend.class);

Skip and Limit

Behaviour similar to Java driver DBCursor.

friends.find().skip(20).as(Friend.class);
friends.find().limit(10).as(Friend.class);

Hint

Behaviour similar to Java driver Find.hint(..).

friends.find().hint("{age: 1}").as(Friend.class);

Oid

As said in the mapping section, _id identifier can be manipulated with a String annotated with @ObjectId. If you want to completly avoid using driver's ObjectId, Jongo offers the Oid class.

import static org.jongo.Oid.withOid;

Friend joe = new Friend(); // @ObjectId String _id
friends.save(joe);
friends.find(withOid(joe.id)).as(Friend.class); // instead of new ObjectId(joe.id)

Query templating

Almost all queries in Jongo can be templated: add anchors # as follow. Binded parameters can be BSON Primitives or any complex type made of those.

friends.find("{name:#, age:#}", "Joe", 18); //→ will produce {name: 'Joe', age: 18}
friends.find("{address: #}", new Address(..)); //→ will marshall Address object
List<String> ages = Lists.newArrayList(22, 63);
friends.find("{age: {$in:#}}", ages); //→ will produce {age: {$in:[22,63]}}

Regular expressions

Classic $regex: 'jo.*' operator works in Jongo. It worth mentioning that Pattern.compile("jo.*") also does. Unluckily, JavaScript syntax /jo*/g is not available in Mongo Java Driver and Jongo cannot use it yet.

collection.findOne("{name: {$regex: #}}", "jo.*").as(Friend.class); 
collection.findOne("{name:#}", Pattern.compile("jo.*")).as(Friend.class);

Count

Behaviour similar to Java driver DBCollection.count(..).

friends.count("{name: 'Joe'}");

Command

Behaviour pretty similar to Java driver db.runCommand(..).

jongo.runCommand("{geoNear: 'friends', near: [48,9]}").as(GeoNearResult.class);

You can map command results to a single result field.

jongo.runCommand("{geoNear: 'friends', near : [48.690,9.140]}")
          .throwOnError()
          .field("results")
          .as(GeoNearResult.class);

Aggregation

Distinct

Distinct syntax is almost the same as Find/FindOne operations. Optional query can be setted using query(..) method

List<String> names = friends.distinct("name").as(String.class);
List<Address> addresses = friends.distinct("address").query("{name: 'Joe'}").as(Address.class);

Aggregation Framework

Careful, this feature is only available in the Mongo v2.2 release. All aggregation operators are supported $project, $match, $limit, $skip, $unwind, $group, $sort and can be piped using and(..) method

collection.aggregate("{$project:{sender:1}}")
          .and("{$match:{tags:'read'}}")
          .and("{$limit:10}")
          .as(Email.class);

Advanced query

Geospacial indexing

Using Mongo geospacial capacities requires an index and some BSON operators.

friends.ensureIndex("{address: '2d'}");
friends.find("{address: {$near: [0, 0], $maxDistance: 5}}").as(Address.class);

ReadPreference & WriteConcern

Read and write behaviors can be configured on a query basis. ReadPreference defines how multiple instances are readed, WriteConcern defines how writes are done.

friends.withWriteConcern(SAFE).withReadPreference(primaryPreferred()).find(..);

Complete query syntax

Mongo uses a behind the scenes syntactic sugar for methods like sort(..), limit(..) and others. The complete expression can also be used in Jongo.

friends.find("{$query: {}, $maxScan: 2}").as(Friend.class);
friends.find("{$query: {}, $orderby: {name: 1}}").as(Friend.class);

Advanced

Performances

Jongo is lighting fast. Not because it is made of ancient wood and magic stones, it just binds Jackson — the fastest Java json (un)marshalling library — to Mongo Java driver with the slightest glue code possible.

Since .3, Jongo uses Mongo Java driver at a deeper level to enhance performance. Instead of converting results into strings and unmarshalling strings into objects, it works at byte[] level (query marhalling works the same since 1.0).

Several microbenchmarks (using Caliper & Yourkit) highlight this release net improvements: Jongo .3 is 6 times faster than Jongo .2, which make it as fast as the driver! Have a look at find microbenchmark and save microbenchmark, and at their implementations (find & save).

Not all benchmarks accurately reflect real world performance; more on microbenchmarks.

Configuring Jongo Mapper

Jongo comes with a custom Jackson configuration that can be extended.

DB db = new Mongo().getDB("dbname");

Jongo jongo = new Jongo(db, 
  new JacksonMapper.Builder()
      .registerModule(new JodaModule())
      .enable(MapperFeature.AUTO_DETECT_GETTERS)
      .withView(Views.Public.class)
      .build()
);

Mapper interface

Jongo constructor comes with an optional parameter: Mapper. This interface has a provided JacksonMapper implementation which defines the defaut (un)marshalling behavior of Jongo. JacksonMapper.Builder allows to alter this configuration as you see fit.

Features on/off

Default JacksonMapper.Builder() comes with a set of predefined features for the embedded Jackson ObjectMapper. Those can be overridden and extended with enable(..) and disable(..) methods.

Modules

Jackson Module is a set of (un)marshalling configuration. Hanlding Joda dates, for example, can be done with this Jackson module and the registerModule(..) method.

Json view

Jackson @JsonView allows to filter an object attributes on (un)marshalling. Once Jongo is configured to use the Public view (as in the previous configuration), the gender attribute will be ignored even if it exists in Mongo.

Careful, when no view is defined, Jackson ignore annotations — which means every attribute is visible. Views can be used for marshalling-only or unmarshalling-only if needed.

public class Friend {
    private String _id;

    @JsonView(Views.Private.class)
    private String gender;
}

public class Views {
    public static class Public {}
    public static class Private extends Public {}
}

MapperModifier

Jongo uses the MapperModifier interface to configure a JacksonMapper. Looking at one implementation give a good feeling of how it can be used to further configure a JacksonMapper.

Reader & WriterCallback

In certain circumstances, modifying Jackson ObjectReader or ObjectWriter will be useful. JacksonMapper offers one default ReaderCallback and one WriterCalback for that purpose, they can be replaced if needed.

For more options, refer to Jackson documentation.

Using a custom Mapper

Instead of Jackson, one can provide his own implementation of Mapper.

public class GsonMapper extends Mapper {
    Marshaller getMarshaller() { ... }
    Unmarshaller getUnmarshaller() { ... }
    ObjectIdUpdater getObjectIdUpdater() { ... }
    QueryFactory getQueryFactory() { ... }
}

Jongo jongo = new Jongo(mongo.getDB("dbname"), new GsonMapper());

Using a custom ResultHandler

If one prefers to manually handle objects (ie. without unmarshaller), he can implements ResultHandler. Each result entity will be passed to the inherited handle(DBObject result) method.

Iterable<Integer> ages = friends.find("{name: 'Joe'}").map(
    new ResultHandler<Integer>() {
        @Override
        public Integer handle(DBObject result) {
            //do manual stuff here
            return result.get("age");
        }
});

Exposing objects through web services

Because Jongo uses Jackson by default, the same objects can be exposed through Jersey (or Spring or any web services library). If you decide to keep ObjectId in pojo attributes, the library (de)serialization have to be configured to support them (using @ObjectId annotation on String avoid this problem).

For Jersey, use this Jackson Provider to expose ObjectId as String (and ignore @ObjectId annotation). For other web services libraries, adapting it won't be to hard.

Release notes (0.4→1.0)

Previous releases: 0.4, 0.3, 0.2, 0.1

query marshalling
full bson marshalling (big thanks to Sylvain Wallez)
GridFS
full compatibility (thanks to Olivier Michallat)
Update
update().merge(pojo) renamed to update().with(pojo)
mappers
extract alternative mappers to jongo-extras repository
fixes
a lot of bug fixing & improvements (thanks to William Delanoue, Alexandre Dutra, Jean-Baptiste Lemée and Sjoerd Mulder)

Download

Jongo relies upon Jackson 2.2.3, Bson4Jackson 2.2.3 and Mongo Java Driver 2.11+. Its Maven dependency comes with the first two, you have to provide a dependency to the driver.

Jongo is deployed into OSS Sonatype (Maven repository hosting service for open source projects).
Add the following dependency to your pom.xml

<dependency>
    <groupId>org.jongo</groupId>
    <artifactId>jongo</artifactId>
    <version>1.0</version>
</dependency>