查询 MongoDB - Java SDK
在此页面上
通过使用Realm Java SDK 的MongoClient和查询API ,您可以直接从 Android应用程序代码查询存储在MongoDB Atlas中的数据。 Atlas App Services提供集合的数据访问规则,以便根据登录用户或每个文档的内容安全地检索结果。
通过以下操作,可以使用MongoDB Atlas cluster RealmSDK 从 Android 应用程序访问链接的 。
注意
本页描述的每个操作都使用查询来匹配操作执行所依据的集合中的某些文档。 当筛选器匹配集合中的多个文档时,除非指定排序参数,否则将以不确定的顺序返回这些文档。 这意味着,如果您没有为 、 findOne()
、 updateOne()
或deleteOne()
函数指定排序,您的操作可能会匹配与查询匹配的任何文档。 有关排序的更多信息,请参阅cursor.sort()。
用例
您可能出于多种原因想要查询 MongoDB 数据源。通过 Atlas Device Sync 处理客户端中的数据并不总是可行或可能的。您可能希望在以下情况下查询 MongoDB:
数据集较大或客户端设备有限制,无法加载整个数据集
您正在创建或更新自定义用户数据
您正在检索未在 Realm 中建模的文档
您的应用程序需要访问没有严格模式的集合
非 Realm 服务会生成要访问的集合
以上查询场景虽未穷尽,却是直接查询 MongoDB 的一些常见使用案例。
先决条件
从 Android 应用程序查询 MongoDB 之前,必须在 App Services App 中设置 MongoDB 数据访问权限。 要了解如何设置后端应用程序以使Realm SDK 查询Atlas ,请参阅 MongoDBAtlas App Services文档中的 设置 数据访问权限 。
设置您的项目
设置您的项目
按照安装 Realm Java SDK指南中的步骤进行操作。
链接 MongoDB Atlas 服务集群
按照链接 MongoDB 数据源指南中的步骤操作。 为您的服务指定一个有意义的名称 — 您需要它来使用 Realm SDK 连接到集群。
导入 Realm 依赖项
CRUD对于远程MongoDB 集合上的 改查操作,您将使用以下一个或多个import
语句:
// 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
实例化 MongoDB 集合句柄
要连接到 MongoDB 实例,您需要一个有权访问 MongoDB 集合的用户。 以此类用户身份登录到您的应用程序,然后使用以下代码实例化本地 MongoDB 集合句柄。
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")
注意
在 MongoDB 中使用自定义类
要在 MongoDB 中使用内置Document
类以外的类,您可以将编解码器添加到MongoCollection
实例中。 在上面的示例中,我们添加PojoCodecProvider
来支持普通旧 Java 对象 (POJO)。 自定义对象支持需要两个编解码器提供程序:
默认编解码器提供程序,为内置 Java 类型提供支持(通过
AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY
访问)PojoCodecProvider
,它会自动创建新的编解码器以支持 POJO 类
SDK 按顺序检查注册表,直到返回所请求类的编解码器。 因此,应首先列出默认编解码器注册表,而PojoCodecProvider
应始终是最后一个 CodecProvider,因为它几乎可以为任何类提供编解码器。
示例数据
以下示例对描述连锁植物商店中的库存的 MongoDB 集合进行操作。 考虑以下文档集合,描述商店中出售的各种植物:
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.")
创建文档
这些代码片段演示了如何从移动应用程序将一个或多个文档插入 MongoDB 集合。 插入操作采用要添加到 MongoDB 的一个或多个文档作为参数,并返回一个RealmResultTask ,该对象解析为包含操作执行结果的对象。
插入单一文档
您可以使用collection.insertOne() 插入单个文档。
以下代码片段会将描述“铃兰”植物的单个文档插入到描述一组商店中待售植物的文档集合中:
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}") } }
运行此代码段将生成类似于以下内容的输出:
V/EXAMPLE: successfully inserted a document with id: BsonObjectId{value=5f19...}
插入多个文档
您可以使用collection.insertMany() 同时插入多个文档。
以下代码段将三个描述植物的文档插入到描述一组商店中待售植物的文档集合中:
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}") } }
运行此代码段将生成类似于以下内容的输出:
V/EXAMPLE: successfully inserted 3 documents into the collection.
读取文档
这些代码片段演示了如何从移动应用程序读取存储在MongoDB集合中的数据。读取操作使用 查询 来指定要从数据库返回哪些文档。读取操作会返回 Task 解析为单个匹配文档(在findOne()
long
count()
的情况下)、 数值(在 的情况下)或允许您遍历匹配文档集合的迭代器(在find()
的情况下)的 )。
查找单个文档
您可以使用collection.findOne() 查找单个文档。
以下代码段从描述一组商店中待售植物的文档集合中查找单个文档,其中植物文档的 type
字段包含string值“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}") } }
运行此代码段会产生类似于以下内容的输出:
V/EXAMPLE: successfully found a document: Plant [id=5f18..., name=venus flytrap, sunlight=full, color=white, type=perennial, partition=Store 42]
查找多个文档
您可以使用collection.find() 查找多个文档。
以下代码段查找描述一组商店中待售植物的文档集合中的所有文档,这些商店包含名为_partition
且值为 "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}") } }
运行此代码段将生成类似于以下内容的输出:
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]
对集合中的文档进行计数
您可以使用 collection.count() 对集合中的文档进行计数。您可以指定一个可选查询来确定要计数的文档。如果未指定查询,则该操作将对集合中的所有文档进行计数。
以下代码段计算描述一组商店中待售植物的文档集合中的文档数量:
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}") } }
运行此代码段将生成类似于以下内容的输出:
V/EXAMPLE: successfully counted, number of documents in the collection: 5
Update Documents
这些代码片段演示了如何从移动应用程序更新存储在 MongoDB 集合中的数据。 更新操作使用查询来指定要更新的文档,并使用更新操作符来描述如何更改与查询匹配的文档。 更新操作返回一个 任务 解析为包含操作执行结果的对象。
更新单份文档
您可以使用collection.updateOne() 更新单个文档。
以下代码段更新文档集合中的单个文档,这些文档描述了一组商店中待售的植物。 此操作查询name
字段包含值“petunia”的文档,并将第一个匹配文档的sunlight
字段的值更改为“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}") } }
运行此代码段将生成类似于以下内容的输出:
V/EXAMPLE: successfully updated a document.
更新多个文档
您可以使用collection.updateMany() 更新多个文档。
以下代码段更新文档集合中的多个文档,这些文档描述一组商店中待售的植物。 此操作查询_partition
字段包含值“Store 47 ”的文档,并将每个匹配文档的_partition
字段的值更改为“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}") } }
运行此代码段将生成类似于以下内容的输出:
V/EXAMPLE: successfully updated 2 documents.
更新或插入文档
如果更新操作与集合中的任何文档都不匹配,则您可以通过将 upsert
选项设置为 true
,自动将单个新文档插入到与更新查询匹配的集合。
以下代码片段更新某文档集合中的一份文档(这些文档描述一组商店中待售的植物),如果没有匹配该查询的文档,则插入一份新文档。此操作查询满足以下条件的文档:
sunlight
字段的值为“full”type
字段的值为“perennial”color
字段的值为“绿色”_partition
字段的值为“Store 47”
由于该代码片段将 upsert
选项设置为 true
,如果没有与查询匹配的文档,MongoDB 就会创建新文档,其中包括查询和指定的更新:
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}") } }
运行此代码段将生成类似于以下内容的输出:
V/EXAMPLE: successfully upserted a document with id: BsonObjectId{value=5f19...}
Delete Documents
这些代码片段演示了如何从移动应用程序中删除存储在 MongoDB 集合中的文档。 删除操作使用查询来指定要删除的文档并返回 Task 解析为包含操作执行结果的对象。
删除单个文档
您可以使用collection.deleteOne() 从集合中删除单个文档。
以下代码段删除描述一组商店中待售植物的文档集合中的一个文档。 此操作查询color
字段值为“green”的文档,并删除与查询匹配的第一个文档:
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}") } }
运行此代码段将生成类似于以下内容的输出:
V/EXAMPLE: successfully deleted a document.
删除多个文档
您可以使用collection.deleteMany() 从集合中删除多个项目。
以下代码片段删除描述一组商店中待售植物的文档集合中的所有文档,这些商店与查询匹配的文档同时包含sunlight
字段值“full”和type
字段值“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}") } }
运行此代码段将生成类似于以下内容的输出:
V/EXAMPLE: succcessfully deleted 2 documents.
注意更改
这些代码片段演示了如何在集合上配置和运行监视操作。
重要
无服务器限制
如果数据源为 Atlas 无服务器实例,则无法监控是否出现更改。MongoDB 无服务器当前不支持变更流,而变更流可用于受监视的集合以侦听是否存在更改。
观察集合中的所有更改
您可以通过调用collection.watch()或collection.watchAsync()打开对集合所做的更改流。 您可以通过将要监控的对象的对象 ID 作为 可变数量的参数传递给该集合,监控对集合中特定文档的更改。
以下代码段监视对plants
集合中任何文档的更改:
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}") } }
运行此代码段将生成类似于以下内容的输出:
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]
监视具有筛选器的集合中的更改
您可以通过调用collection.watchWithFilter()或collection.watchWithFilterAsync()打开对集合中满足特定条件的文档所做的更改流。 这两种方法都接受Document
或BsonDocument
参数作为$match 操作符的查询,以处理在监视集合时发生的每个数据库事件。
以下代码段监视plants
集合中文档的更改,但只有Atlas Triggers 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}") } }
运行此代码段将生成类似于以下内容的输出:
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]
聚合集合中的文档
聚合操作会通过一系列名为聚合管道的数据聚合阶段来运行集合中的所有文档。聚合允许您过滤和转换文档、收集有关相关文档群组的摘要数据以及其他复杂的数据操作。
您可以使用collection.aggregate() 执行聚合管道。
聚合操作接受聚合阶段列表作为输入并返回 Task 解析为管道处理的文档集合。
筛选文档
您可以使用$match阶段根据 Query API查询筛选器来筛选文档。
{ "$match": { "<Field Name>": <Query Expression>, ... } }
例子
以下 $match
阶段会筛选文档,以仅包含 type
字段值等于“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阶段聚合一个或多个文档的摘要数据。 MongoDB 会根据$group
阶段的_id
字段值中定义的表达式对文档进行分组。 您可以通过在字段名称前加上$
前缀来引用特定的文档字段。
以下代码片段将按 type
值对 plants
集合中的所有文档进行分组,并汇总每种类型的数量:
// 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阶段包含或省略文档中的特定字段。 此外,您可以使用聚合操作符计算新字段。 投影有两种工作方式:
显式包含值为 1 的字段。此举的不利影响是会隐式排除所有未指定的字段。
隐式排除值为 0 的字段。此举的不利影响是会隐式包含所有未指定的字段。
这两种投影方法是互斥的:如果显式包含字段,则不能显式排除字段,反之亦然。
注意
_id
字段是一个特例:除非另行显式指定,否则它始终包含在每个查询中。因此,您可以排除具有 0
值的 _id
字段,同时包含其他字段,例如具有 1
的 _partition
。只有排除 _id
字段的特殊情况才允许在一个 $project
阶段中同时进行排除和包含。
{ "$project": { "<Field Name>": <0 | 1 | Expression>, ... } }
例子
以下 $project
阶段会省略 _id
字段,包含 name
字段,并创建一个名为 storeNumber
的新字段。storeNumber
是使用两个聚合运算符生成的:
$split
会将_partition
值分成位于空格字符两端的两个字符串段。例如,以此方式分割值“Store 42”会返回一个包含两个元素的数组:“Store”和“42”。$arrayElemAt
会根据第二个参数从数组中选择特定元素。在本例中,1
值会从$split
运算符生成的数组中选择第二个元素,因为数组索引会从0
开始。例如,传递给此操作的值 ["Store", "42"] 将返回值“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}") } }
向文档添加字段
您可以在$addFields阶段通过聚合操作符添加具有计算值的新字段。
注意
$addFields
与 $project 类似,但不允许包含或省略字段。
例子
以下 $addFields
阶段将创建一个名为 storeNumber
的新字段,其中该字段的值为用于转换 _partition
字段值的两个聚合运算符的输出。
// 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
您可以使用 $unwind 阶段将包含数组的单个文档转换为包含该数组中各个值的多个文档。当您展开数组字段时,MongoDB 会为该数组字段的每个元素复制每个文档一次,但同时还会在每个副本中用数组元素替换数组值。
{ $unwind: { path: <Array Field Path>, includeArrayIndex: <string>, preserveNullAndEmptyArrays: <boolean> } }
例子
下面的 $unwind
阶段为每个文档中 items
数组的每个元素创建一个新文档。该阶段还会在每个新文档中添加一个名为 itemIndex
的字段,指定元素在原始数组中的位置索引:
// 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}") } }
考虑购买集合中的以下文档:
{ _id: 123, customerId: 24601, items: [ { name: "Baseball", quantity: 5 }, { name: "Baseball Mitt", quantity: 1 }, { name: "Baseball Bat", quantity: 1 }, ] }
如果我们将示例 $unwind
阶段应用于此文档,该阶段将输出以下三个文档:
{ _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 } }