Query MongoDB - Java SDK
On this page
- Use Cases
- Prerequisites
- Set Up Your Project
- Set Up Your Project
- Link a MongoDB Atlas Service Cluster
- Import Realm Dependencies
- Instantiate a MongoDB Collection Handle
- Example Data
- Create Documents
- Insert a Single Document
- Insert Multiple Documents
- Read Documents
- Find a Single Document
- Find Multiple Documents
- Count Documents in the Collection
- Update Documents
- Update a Single Document
- Update Multiple Documents
- Upsert Documents
- Delete Documents
- Delete a Single Document
- Delete Multiple Documents
- Watch for Changes
- Watch for Changes in a Collection
- Watch for Changes in a Collection with a Filter
- Aggregate Documents in a Collection
- Filter Documents
- Group Documents
- Project Document Fields
- Add Fields to Documents
- Unwind Array Values
You can query data stored in MongoDB Atlas directly from your Android application code by using the Realm Java SDK's MongoClient with the Query API. Atlas App Services provides data access rules on collections to securely retrieve results based on the logged-in user or the content of each document.
The following actions enable access to a linked MongoDB Atlas cluster from an Android application using the Realm SDK.
Note
Each operation described on this page uses a query to
match certain documents in the collection upon which the operation
executes. When a filter matches multiple documents in a collection,
they are returned in an indeterminate order unless you
specify a sorting parameter. This means that if you do not specify
a sort for the findOne()
, updateOne()
, or deleteOne()
functions, your operation could match any document that matches the
query. For more information on sorting, see
cursor.sort().
Use Cases
There are a variety of reasons you might want to query a MongoDB data source. Working with data in your client via Atlas Device Sync is not always practical or possible. You might want to query MongoDB when:
The data set is large or the client device has constraints against loading the entire data set
You are retrieving documents that are not modeled in Realm
Your app needs to access collections that don't have strict schemas
A non-Realm service generates collections that you want to access
While not exhaustive, these are some common use cases for querying MongoDB directly.
Prerequisites
Before you can query MongoDB from your Android application, you must set up MongoDB Data Access in your App Services App. To learn how to set up your backend App to let the Realm SDK query Atlas, refer to Set Up MongoDB Data Access in the App Services documentation.
Set Up Your Project
Set Up Your Project
Follow the steps in the Install the Realm Java SDK guide.
Link a MongoDB Atlas Service Cluster
Follow the steps in the Link a MongoDB data source guide. Assign your service a meaningful name -- you'll need it to connect to the cluster using the Realm SDK.
Import Realm Dependencies
For CRUD operations on a remote MongoDB collection, you will use one
or more of the following import
statements:
// Base Realm Packages import io.realm.mongodb.App; import io.realm.mongodb.AppConfiguration; // Realm Authentication Packages import io.realm.mongodb.User; import io.realm.mongodb.Credentials; // MongoDB Service Packages import io.realm.mongodb.mongo.MongoClient; import io.realm.mongodb.mongo.MongoDatabase; import io.realm.mongodb.mongo.MongoCollection; // Utility Packages import org.bson.Document;
// Base Realm Packages import io.realm.mongodb.App import io.realm.mongodb.AppConfiguration // Realm Authentication Packages import io.realm.mongodb.User import io.realm.mongodb.Credentials // MongoDB Service Packages import io.realm.mongodb.mongo.MongoClient import io.realm.mongodb.mongo.MongoDatabase import io.realm.mongodb.mongo.MongoCollection // Utility Packages import org.bson.Document
Instantiate a MongoDB Collection Handle
To connect to an instance of MongoDB, you'll need a user with access to a MongoDB collection. Log in to your application as such a user, then use the following code to instantiate a local MongoDB collection handle.
User user = app.currentUser(); MongoClient mongoClient = user.getMongoClient("mongodb-atlas"); MongoDatabase mongoDatabase = mongoClient.getDatabase("plant-data-database"); // registry to handle POJOs (Plain Old Java Objects) CodecRegistry pojoCodecRegistry = fromRegistries(AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY, fromProviders(PojoCodecProvider.builder().automatic(true).build())); MongoCollection<Plant> mongoCollection = mongoDatabase.getCollection( "plant-data-collection", Plant.class).withCodecRegistry(pojoCodecRegistry); Log.v("EXAMPLE", "Successfully instantiated the MongoDB collection handle");
val user = app.currentUser() val mongoClient = user!!.getMongoClient("mongodb-atlas") val mongoDatabase = mongoClient.getDatabase("plant-data-database") // registry to handle POJOs (Plain Old Java Objects) val pojoCodecRegistry = CodecRegistries.fromRegistries( AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY, CodecRegistries.fromProviders( PojoCodecProvider.builder().automatic(true).build())) val mongoCollection = mongoDatabase.getCollection( "plant-data-collection", Plant::class.java).withCodecRegistry(pojoCodecRegistry) Log.v("EXAMPLE", "Successfully instantiated the MongoDB collection handle")
Note
Using Custom Classes with MongoDB
To use classes other than the built-in Document
class with
MongoDB, you can add codecs to your MongoCollection
instances.
In the above example, we add the PojoCodecProvider
to support
Plain Old Java Objects (POJOs). Custom object support requires two
codec providers:
the default codec provider, which provides support for built-in Java types (accessed through
AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY
)PojoCodecProvider
, which automatically creates new codecs to support POJO classes
The SDK checks registries in order until one returns a codec for
the requested class. As a result, you should list the default codec
registry first, and the PojoCodecProvider
should always be the
last CodecProvider since it can provide a codec for almost any
class.
Example Data
The following examples operate on a MongoDB collection that describes inventory in a chain of plant stores. Consider the following collection of documents describing various plants for sale in a store:
import org.bson.codecs.pojo.annotations.BsonProperty; import org.bson.types.ObjectId; public class Plant { private ObjectId id; private String name; private String sunlight; private String color; private String type; private String partition; // empty constructor required for MongoDB Data Access POJO codec compatibility public Plant() {} public Plant(ObjectId id, String name, String sunlight, String color, String type, String partition) { this.id = id; this.name = name; this.sunlight = sunlight; this.color = color; this.type = type; this.partition = partition; } public ObjectId getId() { return id; } public void setId(ObjectId id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSunlight() { return sunlight; } public void setSunlight(String sunlight) { this.sunlight = sunlight; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getPartition() { return partition; } public void setPartition(String partition) { this.partition = partition; } public String toString() { return "Plant [id=" + id + ", name=" + name + ", sunlight=" + sunlight + ", color=" + color + ", type=" + type + ", partition=" + partition + "]"; } }
User user = app.currentUser(); MongoClient mongoClient = user.getMongoClient("mongodb-atlas"); MongoDatabase mongoDatabase = mongoClient.getDatabase("plant-data-database"); // registry to handle POJOs (Plain Old Java Objects) CodecRegistry pojoCodecRegistry = fromRegistries(AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY, fromProviders(PojoCodecProvider.builder().automatic(true).build())); MongoCollection<Plant> mongoCollection = mongoDatabase.getCollection( "plant-data-collection", Plant.class).withCodecRegistry(pojoCodecRegistry); mongoCollection.insertMany(Arrays.asList( new Plant(new ObjectId(), "venus flytrap", "full", "white", "perennial", "Store 42"), new Plant(new ObjectId(), "sweet basil", "partial", "green", "annual", "Store 42"), new Plant(new ObjectId(), "thai basil", "partial", "green", "perennial", "Store 42"), new Plant(new ObjectId(), "helianthus", "full", "yellow", "annual", "Store 42"), new Plant(new ObjectId(), "petunia", "full", "purple", "annual", "Store 47"))); Log.v("EXAMPLE", "Successfully inserted the sample data.");
import org.bson.codecs.pojo.annotations.BsonProperty import org.bson.types.ObjectId open class Plant(val id : ObjectId = ObjectId(), var name : String? = null, var sunlight : String? = null, var color : String? = null, var type : String? = null, "_partition") // specify that this is a field-level annotation ( var partition : String? = null) { override fun toString(): String { return "Plant [id=$id, name=$name, sunlight=$sunlight, color=$color, type=$type, partition=$partition]" } }
val user = app.currentUser() val mongoClient = user!!.getMongoClient("mongodb-atlas") val mongoDatabase = mongoClient.getDatabase("plant-data-database") // registry to handle POJOs (Plain Old Java Objects) val pojoCodecRegistry = CodecRegistries.fromRegistries( AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY, CodecRegistries.fromProviders( PojoCodecProvider.builder().automatic(true).build())) val mongoCollection = mongoDatabase.getCollection( "plant-data-collection", Plant::class.java).withCodecRegistry(pojoCodecRegistry) mongoCollection.insertMany( listOf( Plant( ObjectId(), "venus flytrap", "full", "white", "perennial", "Store 42" ), Plant( ObjectId(), "sweet basil", "partial", "green", "annual", "Store 42" ), Plant( ObjectId(), "thai basil", "partial", "green", "perennial", "Store 42" ), Plant( ObjectId(), "helianthus", "full", "yellow", "annual", "Store 42" ), Plant( ObjectId(), "petunia", "full", "purple", "annual", "Store 47" ) ) ) Log.v("EXAMPLE", "Successfully Successfully inserted the sample data.")
Create Documents
These code snippets demonstrate how to insert one or more documents into a MongoDB collection from a mobile application. Insert operations take a document or documents to add to MongoDB as an argument and return a RealmResultTask that resolves to an object that contains the results of the execution of the operation.
Insert a Single Document
You can insert a single document using collection.insertOne().
The following snippet inserts a single document describing a "lily of the valley" plant into a collection of documents that describe plants for sale in a group of stores:
Plant plant = new Plant( new ObjectId(), "lily of the valley", "full", "white", "perennial", "Store 47"); mongoCollection.insertOne(plant).getAsync(task -> { if (task.isSuccess()) { Log.v("EXAMPLE", "successfully inserted a document with id: " + task.get().getInsertedId()); } else { Log.e("EXAMPLE", "failed to insert documents with: " + task.getError().getErrorMessage()); } });
val plant = Plant( ObjectId(), "lily of the valley", "full", "white", "perennial", "Store 47" ) mongoCollection?.insertOne(plant)?.getAsync { task -> if (task.isSuccess) { Log.v( "EXAMPLE", "successfully inserted a document with id: ${task.get().insertedId}" ) } else { Log.e("EXAMPLE", "failed to insert documents with: ${task.error}") } }
Running this snippet produces output resembling the following:
V/EXAMPLE: successfully inserted a document with id: BsonObjectId{value=5f19...}
Insert Multiple Documents
You can insert multiple documents at the same time using collection.insertMany().
The following snippet inserts three documents describing plants into a collection of documents that describe plants for sale in a group of stores:
List<Plant> plants = Arrays.asList( new Plant(new ObjectId(), "rhubarb", "full", "red", "perennial", "Store 47"), new Plant(new ObjectId(), "wisteria lilac", "partial", "purple", "perennial", "Store 42"), new Plant(new ObjectId(), "daffodil", "full", "yellow", "perennial", "Store 42")); mongoCollection.insertMany(plants).getAsync(task -> { if (task.isSuccess()) { int insertedCount = task.get().getInsertedIds().size(); Log.v("EXAMPLE", "successfully inserted " + insertedCount + " documents into the collection."); } else { Log.e("EXAMPLE", "failed to insert documents with: ", task.getError()); } });
val plants = listOf( Plant( ObjectId(), "rhubarb", "full", "red", "perennial", "Store 47" ), Plant( ObjectId(), "wisteria lilac", "partial", "purple", "perennial", "Store 42" ), Plant( ObjectId(), "daffodil", "full", "yellow", "perennial", "Store 42" ) ) mongoCollection.insertMany(plants).getAsync { task -> if (task.isSuccess) { val insertedCount = task.get().insertedIds.size Log.v( "EXAMPLE", "successfully inserted $insertedCount documents into the collection." ) } else { Log.e("EXAMPLE", "failed to insert documents with: ${task.error}") } }
Running this snippet produces output resembling the following:
V/EXAMPLE: successfully inserted 3 documents into the collection.
Read Documents
These code snippets demonstrate how to read data stored in a MongoDB
collection from a mobile application. Read operations use queries to specify which documents to
return from the database. Read operations return a
Task that resolves to
either a single matched document (in the case of findOne()
), a long
numeric value (in the case of count()
) or an iterator that allows you to
traverse the collection of matched documents (in the case of find()
).
Find a Single Document
You can find a single document using collection.findOne().
The following snippet finds a single document from the a
collection of documents that describe plants for sale in a group of stores where the plant document's type
field contains the string value "perennial":
Document queryFilter = new Document("type", "perennial"); mongoCollection.findOne(queryFilter).getAsync(task -> { if (task.isSuccess()) { Plant result = task.get(); Log.v("EXAMPLE", "successfully found a document: " + result); } else { Log.e("EXAMPLE", "failed to find document with: ", task.getError()); } });
val queryFilter = Document("type", "perennial") mongoCollection.findOne(queryFilter) .getAsync { task -> if (task.isSuccess) { val result = task.get() Log.v("EXAMPLE", "successfully found a document: $result") } else { Log.e("EXAMPLE", "failed to find document with: ${task.error}") } }
Running this snippet produces output similar to the following:
V/EXAMPLE: successfully found a document: Plant [id=5f18..., name=venus flytrap, sunlight=full, color=white, type=perennial, partition=Store 42]
Find Multiple Documents
You can find multiple documents using collection.find().
The following snippet finds all documents in a
collection of documents that describe plants for sale in a group of stores that contain a field named
_partition
with a value of "Store 42":
Document queryFilter = new Document("_partition", "Store 42"); RealmResultTask<MongoCursor<Plant>> findTask = mongoCollection.find(queryFilter).iterator(); findTask.getAsync(task -> { if (task.isSuccess()) { MongoCursor<Plant> results = task.get(); Log.v("EXAMPLE", "successfully found all plants for Store 42:"); while (results.hasNext()) { Log.v("EXAMPLE", results.next().toString()); } } else { Log.e("EXAMPLE", "failed to find documents with: ", task.getError()); } });
val queryFilter = Document("_partition", "Store 42") val findTask = mongoCollection.find(queryFilter).iterator() findTask.getAsync { task -> if (task.isSuccess) { val results = task.get() Log.v("EXAMPLE", "successfully found all plants for Store 42:") while (results.hasNext()) { Log.v("EXAMPLE", results.next().toString()) } } else { Log.e("EXAMPLE", "failed to find documents with: ${task.error}") } }
Running this snippet produces output resembling the following:
V/EXAMPLE: successfully found all plants for Store 42: V/EXAMPLE: Plant [id=5f18..., name=venus flytrap, sunlight=full, color=white, type=perennial, partition=Store 42] V/EXAMPLE: Plant [id=5f18..., name=sweet basil, sunlight=partial, color=green, type=annual, partition=Store 42] V/EXAMPLE: Plant [id=5f18..., name=thai basil, sunlight=partial, color=green, type=perennial, partition=Store 42] V/EXAMPLE: Plant [id=5f18..., name=helianthus, sunlight=full, color=yellow, type=annual, partition=Store 42]
Count Documents in the Collection
You can count documents in a collection using collection.count(). You can specify an optional query to determine which documents to count. If you don't specify a query, the action counts all documents in the collection.
The following snippet counts the number of documents in a collection of documents that describe plants for sale in a group of stores:
mongoCollection.count().getAsync(task -> { if (task.isSuccess()) { long count = task.get(); Log.v("EXAMPLE", "successfully counted, number of documents in the collection: " + count); } else { Log.e("EXAMPLE", "failed to count documents with: ", task.getError()); } });
mongoCollection.count().getAsync { task -> if (task.isSuccess) { val count = task.get() Log.v("EXAMPLE", "successfully counted, number of documents in the collection: $count") } else { Log.e("EXAMPLE", "failed to count documents with: ${task.error}") } }
Running this snippet produces output resembling the following:
V/EXAMPLE: successfully counted, number of documents in the collection: 5
Update Documents
These code snippets demonstrate how to update data stored in a MongoDB collection from a mobile application. Update operations use queries to specify which documents to update and update operators to describe how to mutate documents that match the query. Update operations return a Task that resolves to an object that contains the results of the execution of the operation.
Update a Single Document
You can update a single document using collection.updateOne().
The following snippet updates a single document in a
collection of documents that describe plants for sale in a group of stores. This operation queries for a document
where the name
field contains the value "petunia" and changes the value
of the first matched document's sunlight
field to "partial":
Document queryFilter = new Document("name", "petunia"); Document updateDocument = new Document("$set", new Document("sunlight", "partial")); mongoCollection.updateOne(queryFilter, updateDocument).getAsync(task -> { if (task.isSuccess()) { long count = task.get().getModifiedCount(); if (count == 1) { Log.v("EXAMPLE", "successfully updated a document."); } else { Log.v("EXAMPLE", "did not update a document."); } } else { Log.e("EXAMPLE", "failed to update document with: ", task.getError()); } });
val queryFilter = Document("name", "petunia") val updateDocument = Document("\$set", Document("sunlight", "partial")) mongoCollection.updateOne(queryFilter, updateDocument).getAsync { task -> if (task.isSuccess) { val count = task.get().modifiedCount if (count == 1L) { Log.v("EXAMPLE", "successfully updated a document.") } else { Log.v("EXAMPLE", "did not update a document.") } } else { Log.e("EXAMPLE", "failed to update document with: ${task.error}") } }
Running this snippet produces output resembling the following:
V/EXAMPLE: successfully updated a document.
Update Multiple Documents
You can update multiple documents using collection.updateMany().
The following snippet updates multiple documents in a
collection of documents that describe plants for sale in a group of stores. This operation queries for documents
where the _partition
field contains the value "Store 47" and changes
the value of the _partition
field of each matching document to "Store 51":
Document queryFilter = new Document("_partition", "Store 47"); Document updateDocument = new Document("$set", new Document("_partition", "Store 51")); mongoCollection.updateMany(queryFilter, updateDocument).getAsync(task -> { if (task.isSuccess()) { long count = task.get().getModifiedCount(); if (count != 0) { Log.v("EXAMPLE", "successfully updated " + count + " documents."); } else { Log.v("EXAMPLE", "did not update any documents."); } } else { Log.e("EXAMPLE", "failed to update documents with: ", task.getError()); } });
val queryFilter = Document("_partition", "Store 47") val updateDocument = Document("\$set", Document("_partition", "Store 51")) mongoCollection.updateMany(queryFilter, updateDocument).getAsync { task -> if (task.isSuccess) { val count = task.get().modifiedCount if (count != 0L) { Log.v("EXAMPLE", "successfully updated $count documents.") } else { Log.v("EXAMPLE", "did not update any documents.") } } else { Log.e("EXAMPLE", "failed to update documents with: ${task.error}") } }
Running this snippet produces output resembling the following:
V/EXAMPLE: successfully updated 2 documents.
Upsert Documents
If an update operation does not match any document in the collection,
you can automatically insert a single new document into the collection
that matches the update query by setting the upsert
option to
true
.
The following snippet updates a document in a collection of documents that describe plants for sale in a group of stores or inserts a new document if no document matches the query. This operation queries for documents where:
the
sunlight
field has a value of "full"the
type
field has a value of "perennial"the
color
field has a value of "green"the
_partition
field has a value of "Store 47"
Because this snippet sets the upsert
option to true
, if no
document matches the query, MongoDB creates a new document that includes
both the query and specified updates:
Document queryFilter = new Document("sunlight", "full") .append("type", "perennial") .append("color", "green") .append("_partition", "Store 47"); Document updateDocument = new Document("$set", new Document("name", "sweet basil")); UpdateOptions updateOptions = new UpdateOptions().upsert(true); mongoCollection.updateOne(queryFilter, updateDocument, updateOptions).getAsync(task -> { if (task.isSuccess()) { if(task.get().getUpsertedId() != null) { Log.v("EXAMPLE", "successfully upserted a document with id " + task.get().getUpsertedId()); } else { Log.v("EXAMPLE", "successfully updated a document."); } } else { Log.e("EXAMPLE", "failed to update or insert document with: ", task.getError()); } });
val queryFilter = Document("sunlight", "full") .append("type", "perennial") .append("color", "green") .append("_partition", "Store 47") val updateDocument = Document("\$set", Document("name", "sweet basil")) val updateOptions = UpdateOptions().upsert(true) mongoCollection.updateOne(queryFilter, updateDocument, updateOptions) .getAsync { task -> if (task.isSuccess) { if (task.get().upsertedId != null) { Log.v("EXAMPLE", "successfully upserted a document with id ${task.get().upsertedId}") } else { Log.v("EXAMPLE", "successfully updated a document.") } } else { Log.e("EXAMPLE", "failed to update or insert document with: ${task.error}") } }
Running this snippet produces output resembling the following:
V/EXAMPLE: successfully upserted a document with id: BsonObjectId{value=5f19...}
Delete Documents
These code snippets demonstrate how to delete documents that are stored in a MongoDB collection from a mobile application. Delete operations use a query to specify which documents to delete and return a Task that resolves to an object that contains the results of the execution of the operation.
Delete a Single Document
You can delete a single document from a collection using collection.deleteOne().
The following snippet deletes one document in a
collection of documents that describe plants for sale in a group of stores. This operation queries for a
document where the color
field has a value of "green" and deletes
the first document that matches the query:
Document queryFilter = new Document("color", "green"); mongoCollection.deleteOne(queryFilter).getAsync(task -> { if (task.isSuccess()) { long count = task.get().getDeletedCount(); if (count == 1) { Log.v("EXAMPLE", "successfully deleted a document."); } else { Log.v("EXAMPLE", "did not delete a document."); } } else { Log.e("EXAMPLE", "failed to delete document with: ", task.getError()); } });
val queryFilter = Document("color", "green") mongoCollection.deleteOne(queryFilter).getAsync { task -> if (task.isSuccess) { val count = task.get().deletedCount if (count == 1L) { Log.v("EXAMPLE", "successfully deleted a document.") } else { Log.v("EXAMPLE", "did not delete a document.") } } else { Log.e("EXAMPLE", "failed to delete document with: ${task.error}") } }
Running this snippet produces output resembling the following:
V/EXAMPLE: successfully deleted a document.
Delete Multiple Documents
You can delete multiple items from a collection using collection.deleteMany().
The following snippet deletes all documents in a
collection of documents that describe plants for sale in a group of stores
that match the query that matches documents containing both
a sunlight
field value of "full" and a type
field value of
"annual".
Document queryFilter = new Document("sunlight", "full") .append("type", "annual"); mongoCollection.deleteMany(queryFilter).getAsync(task -> { if (task.isSuccess()) { long count = task.get().getDeletedCount(); if (count != 0) { Log.v("EXAMPLE", "successfully deleted " + count + " documents."); } else { Log.v("EXAMPLE", "did not delete any documents."); } } else { Log.e("EXAMPLE", "failed to delete documents with: ", task.getError()); } });
val queryFilter = Document("sunlight", "full").append("type", "annual") mongoCollection.deleteMany(queryFilter).getAsync { task -> if (task.isSuccess) { val count = task.get().deletedCount if (count != 0L) { Log.v("EXAMPLE", "successfully deleted $count documents.") } else { Log.v("EXAMPLE", "did not delete any documents.") } } else { Log.e("EXAMPLE", "failed to delete documents with: ${task.error}") } }
Running this snippet produces output resembling the following:
V/EXAMPLE: succcessfully deleted 2 documents.
Watch for Changes
These code snippets demonstrate how to configure and run watch operations on a collection.
Important
Serverless Limitations
You cannot watch for changes if the data source is an Atlas serverless instance. MongoDB serverless currently does not support change streams, which are used on watched collections to listen for changes.
Watch for Changes in a Collection
You can open a stream of changes made to a collection by calling collection.watch() or collection.watchAsync(). You can watch for changes to specific documents in a collection by passing the object ids of the objects you would like to monitor as a variable number of arguments.
The following snippet watches for changes to any documents in the
plants
collection:
RealmEventStreamAsyncTask<Plant> watcher = mongoCollection.watchAsync(); watcher.get(result -> { if (result.isSuccess()) { Log.v("EXAMPLE", "Event type: " + result.get().getOperationType() + " full document: " + result.get().getFullDocument()); } else { Log.e("EXAMPLE", "failed to subscribe to changes in the collection with : ", result.getError()); } }); Plant triffid = new Plant( new ObjectId(), "triffid", "low", "green", "perennial", "Store 47"); mongoCollection.insertOne(triffid).getAsync(task -> { if (task.isSuccess()) { BsonObjectId insertedId = task.get().getInsertedId().asObjectId(); Log.v("EXAMPLE", "successfully inserted a document with id " + insertedId); } else { Log.e("EXAMPLE", "failed to insert document with: ", task.getError()); } });
val watcher = mongoCollection.watchAsync() watcher[{ result -> if (result.isSuccess) { Log.v("EXAMPLE", "Event type: ${result.get().operationType} full document: ${result.get().fullDocument}") } else { Log.e("EXAMPLE", "failed to subscribe to changes in the collection with : ${result.error}") } }] val triffid = Plant( ObjectId(), "triffid", "low", "green", "perennial", "Store 47" ) mongoCollection.insertOne(triffid).getAsync { task -> if (task.isSuccess) { val insertedId = task.get().insertedId.asObjectId() Log.v("EXAMPLE", "successfully inserted a document with id $insertedId") } else { Log.e("EXAMPLE", "failed to insert document with: ${task.error}") } }
Running this snippet produces output resembling the following:
V/EXAMPLE: successfully inserted a document with id BsonObjectId{value=5f6bb...} V/EXAMPLE: Event type: INSERT full document: Plant [id=5f6bb..., name=triffid, sunlight=low, color=green, type=perennial, partition=Store 47]
Watch for Changes in a Collection with a Filter
You can open a stream of changes made to documents in a collection that
fulfill certain criteria by calling collection.watchWithFilter() or
collection.watchWithFilterAsync().
Both methods accept a Document
or BsonDocument
parameter that is
used as the query of a $match operator to process each
database event that occurs while watching the
collection.
The following snippet watches for changes to documents in the
plants
collection, but only triggers the provided callback for
events corresponding to documents belonging to the partition named
"Store 42":
RealmEventStreamAsyncTask<Plant> watcher = mongoCollection .watchWithFilterAsync(new Document("fullDocument._partition", "Store 42")); watcher.get(result -> { if (result.isSuccess()) { Log.v("EXAMPLE", "Event type: " + result.get().getOperationType() + " full document: " + result.get().getFullDocument()); } else { Log.e("EXAMPLE", "failed to subscribe to filtered changes in the collection with : ", result.getError()); } }); List<Plant> plants = Arrays.asList( new Plant( new ObjectId(), "triffid", "low", "green", "perennial", "Store 47"), new Plant( new ObjectId(), "venomous tentacula", "low", "brown", "annual", "Store 42" )); mongoCollection.insertMany(plants).getAsync(task -> { if (task.isSuccess()) { int insertedCount = task.get().getInsertedIds().size(); Log.v("EXAMPLE", "successfully inserted " + insertedCount + " documents into the collection."); } else { Log.e("EXAMPLE", "failed to insert documents with: ", task.getError()); } });
val watcher = mongoCollection .watchWithFilterAsync(Document("fullDocument._partition", "Store 42")) watcher[{ result -> if (result.isSuccess) { Log.v("EXAMPLE", "Event type: ${result.get().operationType} full document: ${result.get().fullDocument}") } else { Log.e("EXAMPLE", "failed to subscribe to filtered changes in the collection with : ${result.error}") } }] val plants = listOf( Plant( ObjectId(), "triffid", "low", "green", "perennial", "Store 47" ), Plant( ObjectId(), "venomous tentacula", "low", "brown", "annual", "Store 42" ) ) mongoCollection.insertMany(plants).getAsync { task -> if (task.isSuccess) { val insertedCount = task.get().insertedIds.size Log.v("EXAMPLE", "successfully inserted $insertedCount documents into the collection.") } else { Log.e("EXAMPLE", "failed to insert documents with: ${task.error}") } }
Running this snippet produces output resembling the following:
V/EXAMPLE: successfully inserted 2 documents into the collection V/EXAMPLE: Event type: INSERT full document: Plant [id=5f6bb..., name=venomous tentacula, sunlight=low, color=brown, type=annual, partition=Store 42]
Aggregate Documents in a Collection
Aggregation operations run all documents in a collection through a series of data aggregation stages called an aggregation pipeline. Aggregation allows you to filter and transform documents, collect summary data about groups of related documents, and other complex data operations.
You can execute an aggregation pipeline using collection.aggregate().
An aggregation operation accepts a list of aggregation stages as input and returns a Task that resolves to a collection of documents processed by the pipeline.
Filter Documents
You can use the $match stage to filter documents according to a Query API query filter.
{ "$match": { "<Field Name>": <Query Expression>, ... } }
Example
The following $match
stage filters documents to include
only those where the type
field has a value equal to "perennial":
// create an aggregation pipeline List<Document> pipeline = Arrays.asList( new Document("$match", new Document("type", new Document("$eq", "perennial")))); // query mongodb using the pipeline RealmResultTask<MongoCursor<Document>> aggregationTask = mongoCollection.aggregate(pipeline).iterator(); // handle success or failure of the query aggregationTask.getAsync(task -> { if (task.isSuccess()) { MongoCursor<Document> results = task.get(); // iterate over and print the results to the log Log.v("EXAMPLE", "successfully aggregated the plants. Results:"); while (results.hasNext()) { Log.v("EXAMPLE", results.next().toString()); } } else { Log.e("EXAMPLE", "failed to aggregate documents with: ", task.getError()); } });
// create an aggregation pipeline val pipeline = listOf( Document("\$match", Document("type", Document("\$eq", "perennial") ) ) ) // query mongodb using the pipeline val aggregationTask = mongoCollection.aggregate(pipeline).iterator() // handle success or failure of the query aggregationTask.getAsync { task: App.Result<MongoCursor<Document>> -> if (task.isSuccess) { val results = task.get() // iterate over and print the results to the log Log.v("EXAMPLE", "successfully aggregated the plants. Results:") while (results.hasNext()) { Log.v("EXAMPLE", results.next().toString()) } } else { Log.e("EXAMPLE", "failed to aggregate documents with: ${task.error}") } }
Group Documents
You can use the $group stage to aggregate summary
data for one or more documents. MongoDB groups documents based
on the expression defined in the _id
field value of the $group
stage. You can reference a specific document field by prefixing the
field name with a $
.
The following snippet groups all documents in the plants
collection by their type
value and aggregates a count of the
number of each type:
// create an aggregation pipeline List<Document> pipeline = Arrays.asList( new Document("$group", new Document("_id", "$type") .append("totalCount", new Document("$sum", 1)))); // query mongodb using the pipeline RealmResultTask<MongoCursor<Document>> aggregationTask = mongoCollection.aggregate(pipeline).iterator(); // handle success or failure of the query aggregationTask.getAsync(task -> { if (task.isSuccess()) { MongoCursor<Document> results = task.get(); // iterate over and print the results to the log Log.v("EXAMPLE", "successfully aggregated the plants. Results:"); while (results.hasNext()) { Log.v("EXAMPLE", results.next().toString()); } } else { Log.e("EXAMPLE", "failed to aggregate documents with: ", task.getError()); } });
// create an aggregation pipeline val pipeline = listOf( Document("\$group", Document("_id", "\$type") .append("totalCount", Document("\$sum", 1)) ) ) // query mongodb using the pipeline val aggregationTask = mongoCollection.aggregate(pipeline).iterator() // handle success or failure of the query aggregationTask.getAsync { task: App.Result<MongoCursor<Document>> -> if (task.isSuccess) { val results = task.get() // iterate over and print the results to the log Log.v("EXAMPLE", "successfully aggregated the plants. Results:") while (results.hasNext()) { Log.v("EXAMPLE", results.next().toString()) } } else { Log.e("EXAMPLE", "failed to aggregate documents with: ${task.error}") } }
Project Document Fields
You can use the $project stage to include or omit specific fields from documents. Additionally, you can calculate new fields using aggregation operators. Projections work in two ways:
Explicitly include fields with a value of 1. This has the side-effect of implicitly excluding all unspecified fields.
Implicitly exclude fields with a value of 0. This has the side-effect of implicitly including all unspecified fields.
These two methods of projection are mutually exclusive: if you explicitly include fields, you cannot explicitly exclude fields, and vice versa.
Note
The _id
field is a special case: it is always included in every
query unless explicitly specified otherwise. For this reason, you
can exclude the _id
field with a 0
value while simultaneously
including other fields, like _partition
, with a 1
. Only the
special case of exclusion of the _id
field allows both exclusion
and inclusion in one $project
stage.
{ "$project": { "<Field Name>": <0 | 1 | Expression>, ... } }
Example
The following $project
stage omits the _id
field, includes
the name
field, and creates a new field named storeNumber
.
The storeNumber
is generated using two aggregation operators:
$split
separates the_partition
value into two string segments surrounding the space character. For example, the value "Store 42" split in this way returns an array with two elements: "Store" and "42".$arrayElemAt
selects a specific element from an array based on the second argument. In this case, the value1
selects the second element from the array generated by the$split
operator since arrays index from0
. For example, the value ["Store", "42"] passed to this operation would return a value of "42".
// create an aggregation pipeline List<Document> pipeline = Arrays.asList( new Document("$project", new Document("_id", 0) .append("name", 1) .append("storeNumber", new Document("$arrayElemAt", Arrays.asList( new Document("$split", Arrays.asList("$_partition", " ")), 1))))); // query mongodb using the pipeline RealmResultTask<MongoCursor<Document>> aggregationTask = mongoCollection.aggregate(pipeline).iterator(); // handle success or failure of the query aggregationTask.getAsync(task -> { if (task.isSuccess()) { MongoCursor<Document> results = task.get(); // iterate over and print the results to the log Log.v("EXAMPLE", "successfully aggregated the plants. Results:"); while (results.hasNext()) { Log.v("EXAMPLE", results.next().toString()); } } else { Log.e("EXAMPLE", "failed to aggregate documents with: ", task.getError()); } });
// create an aggregation pipeline val pipeline = listOf( Document("\$project", Document("_id", 0) .append("name", 1) .append("storeNumber", Document("\$arrayElemAt", listOf( Document("\$split", listOf( "\$_partition", " " ) ), 1 ) ) ) ) ) // query mongodb using the pipeline val aggregationTask = mongoCollection.aggregate(pipeline).iterator() // handle success or failure of the query aggregationTask.getAsync { task: App.Result<MongoCursor<Document>> -> if (task.isSuccess) { val results = task.get() // iterate over and print the results to the log Log.v("EXAMPLE", "successfully aggregated the plants. Results:") while (results.hasNext()) { Log.v("EXAMPLE", results.next().toString()) } } else { Log.e("EXAMPLE", "failed to aggregate documents with: ${task.error}") } }
Add Fields to Documents
You can use the $addFields stage to add new fields with calculated values using aggregation operators.
Note
$addFields
is similar to $project but does not allow you to
include or omit fields.
Example
The following $addFields
stage creates a new field named
storeNumber
where the value is the output of two aggregate operators
that transform the value of the _partition
field.
// create an aggregation pipeline List<Document> pipeline = Arrays.asList( new Document("$addFields", new Document("storeNumber", new Document("$arrayElemAt", Arrays.asList( new Document("$split", Arrays.asList( "$_partition", " ")), 1))))); // query mongodb using the pipeline RealmResultTask<MongoCursor<Document>> aggregationTask = mongoCollection.aggregate(pipeline).iterator(); // handle success or failure of the query aggregationTask.getAsync(task -> { if (task.isSuccess()) { MongoCursor<Document> results = task.get(); // iterate over and print the results to the log Log.v("EXAMPLE", "successfully aggregated the plants. Results:"); while (results.hasNext()) { Log.v("EXAMPLE", results.next().toString()); } } else { Log.e("EXAMPLE", "failed to aggregate documents with: ", task.getError()); } });
// create an aggregation pipeline val pipeline = listOf( Document("\$addFields", Document("storeNumber", Document("\$arrayElemAt", listOf( Document("\$split", listOf( "\$_partition", " " ) ), 1 ) ) ) ) ) // query mongodb using the pipeline val aggregationTask = mongoCollection.aggregate(pipeline).iterator() // handle success or failure of the query aggregationTask.getAsync { task: App.Result<MongoCursor<Document>> -> if (task.isSuccess) { val results = task.get() // iterate over and print the results to the log Log.v("EXAMPLE", "successfully aggregated the plants. Results:") while (results.hasNext()) { Log.v("EXAMPLE", results.next().toString()) } } else { Log.e("EXAMPLE", "failed to aggregate documents with: ${task.error}") } }
Unwind Array Values
You can use the $unwind stage to transform a single document containing an array into multiple documents containing individual values from that array. When you unwind an array field, MongoDB copies each document once for each element of the array field but replaces the array value with the array element in each copy.
{ $unwind: { path: <Array Field Path>, includeArrayIndex: <string>, preserveNullAndEmptyArrays: <boolean> } }
Example
The following $unwind
stage creates a new document for each
element of the items
array in each document. It also adds a field
called itemIndex
to each new document that specifies the
element's position index in the original array:
// create an aggregation pipeline List<Document> pipeline = Arrays.asList( new Document("$unwind", new Document("path", "$items") .append("includeArrayIndex", "itemIndex"))); // query mongodb using the pipeline RealmResultTask<MongoCursor<Document>> aggregationTask = mongoCollection.aggregate(pipeline).iterator(); // handle success or failure of the query aggregationTask.getAsync(task -> { if (task.isSuccess()) { MongoCursor<Document> results = task.get(); // iterate over and print the results to the log Log.v("EXAMPLE", "successfully aggregated the plants. Results:"); while (results.hasNext()) { Log.v("EXAMPLE", results.next().toString()); } } else { Log.e("EXAMPLE", "failed to aggregate documents with: ", task.getError()); } });
// create an aggregation pipeline val pipeline = listOf( Document("\$unwind", Document("path", "\$items") .append("includeArrayIndex", "itemIndex")) ) // query mongodb using the pipeline val aggregationTask = mongoCollection.aggregate(pipeline).iterator() // handle success or failure of the query aggregationTask.getAsync { task: App.Result<MongoCursor<Document>> -> if (task.isSuccess) { val results = task.get() // iterate over and print the results to the log Log.v("EXAMPLE", "successfully aggregated the plants. Results:") while (results.hasNext()) { Log.v("EXAMPLE", results.next().toString()) } } else { Log.e("EXAMPLE", "failed to aggregate documents with: ${task.error}") } }
Consider the following document from the a collection of purchases:
{ _id: 123, customerId: 24601, items: [ { name: "Baseball", quantity: 5 }, { name: "Baseball Mitt", quantity: 1 }, { name: "Baseball Bat", quantity: 1 }, ] }
If we apply the example $unwind
stage to this document, the stage
outputs the following three documents:
{ _id: 123, customerId: 24601, itemIndex: 0, items: { name: "Baseball", quantity: 5 } }, { _id: 123, customerId: 24601, itemIndex: 1, items: { name: "Baseball Mitt", quantity: 1 } }, { _id: 123, customerId: 24601, itemIndex: 2, items: { name: "Baseball Bat", quantity: 1 } }