導(dǎo)讀
mongodb-java-driver是mongodb的Java驅(qū)動(dòng)項(xiàng)目的畴。
本文是對(duì)MongoDB-java-driver官方文檔 MongoDB Async Driver Quick Tour 的翻譯(原創(chuàng)翻譯)。
mongodb-java-driver 從3.0版本開始同時(shí)支持同步耍共、異步方式(分別是不同的驅(qū)動(dòng)應(yīng)用)诡蜓。異步的好處熬甫,眾所周知,就是支持快速蔓罚、非阻塞式的IO操作,可以提高處理速度瞻颂。
請(qǐng)注意:本文僅介紹異步驅(qū)動(dòng)的使用指南豺谈。同步驅(qū)動(dòng)官方文檔:mongo-java-driver ,需要了解的朋友贡这,請(qǐng)移駕茬末。
安裝
簡(jiǎn)單提下安裝說明。
注:MongoDB 異步驅(qū)動(dòng)需要依賴Netty 或 Java 7盖矫。
如果你的項(xiàng)目是maven項(xiàng)目丽惭,只需在pom.xml中添加如下依賴:
<dependencies>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-async</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
你也可以點(diǎn)擊鏈接直接下載jar包: 下載點(diǎn)這里 。
分割線辈双,下面是 MongoDB Async Driver Quick Tour 的譯文责掏。
MongoDB 異步驅(qū)動(dòng)快速指南
以下的代碼片段來自于 async driver source 的范例代碼 QuickTour.java
。
注意
如何安裝MongoDB異步驅(qū)動(dòng)請(qǐng)參考 安裝指導(dǎo) 湃望。
執(zhí)行異步回調(diào)
MongoDB異步驅(qū)動(dòng)利用Netty或Java7的AsynchronousSocketChannel
來提供一個(gè)支持異步的API换衬,以支持快速的、非阻塞式的IO操作证芭。
該API形式和MongoDB同步驅(qū)動(dòng)的新API保持一致瞳浦,但是任何會(huì)導(dǎo)致網(wǎng)絡(luò)IO的方法都會(huì)有一個(gè)SingleResponseCallback
并且會(huì)立即返回,其中T
是響應(yīng)對(duì)于該文檔的類型的任何方法废士。
SingleResponseCallback
回調(diào)接口需要實(shí)現(xiàn)一個(gè)簡(jiǎn)單方法onResult(T result, Throwable t)
叫潦,這個(gè)方法在操作完成時(shí)被調(diào)用。其中官硝,如果操作成功矗蕊, result
參數(shù)包含著操作結(jié)果四敞;如果操作失敗,t
中包含著拋出的異常信息拔妥。
重要
在SingleResponseCallback
的實(shí)現(xiàn)中檢查錯(cuò)誤并適當(dāng)處理錯(cuò)誤是十分重要的忿危。下面的錯(cuò)誤檢查僅為簡(jiǎn)便起見而省略。
創(chuàng)建一個(gè)連接
下面的例子展示多種方法去鏈接本地機(jī)器上的mydb
數(shù)據(jù)庫没龙。詳情參考 MongoClients.create
API手冊(cè)铺厨。
// 直接連接默認(rèn)服務(wù)host和端口,即 localhost:27017
MongoClient mongoClient = MongoClients.create();
// 使用一個(gè)字符串
MongoClient mongoClient = MongoClients.create("mongodb://localhost");
// 使用一個(gè)ConnectionString
MongoClient mongoClient = MongoClients.create(new ConnectionString("mongodb://localhost"));
// 使用MongoClientSettings
ClusterSettings clusterSettings = ClusterSettings.builder().hosts(asList(new ServerAddress("localhost"))).build();
MongoClientSettings settings = MongoClientSettings.builder().clusterSettings(clusterSettings).build();
MongoClient mongoClient = MongoClients.create(settings);
MongoDatabase database = mongoClient.getDatabase("mydb");
此時(shí)硬纤,database
對(duì)象是一個(gè)MongoDB 服務(wù)器中指定數(shù)據(jù)庫的連接解滓。
注意
getDatabase("mydb")
方法并沒有回調(diào),因?yàn)樗鼪]有涉及網(wǎng)絡(luò)IO操作筝家。一個(gè) MongoDatabase
實(shí)例提供了與數(shù)據(jù)庫進(jìn)行交互的方法洼裤,若數(shù)據(jù)庫不存在,它會(huì)在插入數(shù)據(jù)時(shí)創(chuàng)建一個(gè)新的數(shù)據(jù)庫溪王。例如腮鞍,創(chuàng)建一個(gè) collection 或插入 document(這些確實(shí)需要回調(diào),因?yàn)樾枰婕熬W(wǎng)絡(luò)IO)莹菱。
MongoClient
MongoClient
實(shí)例實(shí)際上代表了一個(gè)數(shù)據(jù)庫的連接池移国;即使要并發(fā)執(zhí)行異步操作,你也僅僅需要一個(gè) MongoClient
實(shí)例道伟。
重要
一般情況下迹缀,在一個(gè)指定的數(shù)據(jù)庫集群中僅需要?jiǎng)?chuàng)建一個(gè)MongoClient
實(shí)例,并通過你的應(yīng)用使用它蜜徽。
當(dāng)創(chuàng)建多個(gè)實(shí)例時(shí):
- 所有的資源使用限制(例如最大連接數(shù))適用于每個(gè)
MongoClient
實(shí)例 - 銷毀實(shí)例時(shí)祝懂,請(qǐng)確保調(diào)用
MongoClient.close()
清理資源。
獲得一個(gè) collection
要獲得一個(gè) collection 拘鞋,你需要在 getCollection(String collectionName)
方法中指定 collection 的名字:
下面的例子獲得名為 test
的collection :
MongoCollection<Document> collection = database.getCollection("test");
添加一個(gè) document
一旦你有了collection對(duì)象砚蓬,你就可以向collection中插入document。例如掐禁,考慮如下的json形式document怜械;document中含包含了一個(gè)名為 info
的子document。
{
"name" : "MongoDB",
"type" : "database",
"count" : 1,
"info" : {
x : 203,
y : 102
}
}
要?jiǎng)?chuàng)建document傅事,需要使用 Document 類缕允。你可以使用這個(gè)類來創(chuàng)建嵌入式的document。
Document doc = new Document("name", "MongoDB")
.append("type", "database")
.append("count", 1)
.append("info", new Document("x", 203).append("y", 102));
要向 collection 中插入 document 蹭越,需要使用 insertOne()
方法障本。
collection.insertOne(doc, new SingleResultCallback<Void>() {
@Override
public void onResult(final Void result, final Throwable t) {
System.out.println("Inserted!");
}
});
SingleResponseCallback
是一個(gè) 函數(shù)式接口 并且它可以以lambda方式實(shí)現(xiàn)(前提是你的APP工作在JDK8):
collection.insertOne(doc, (Void result, final Throwable t) -> System.out.println("Inserted!"));
一旦document成功插入,onResult 回調(diào)方法會(huì)被調(diào)用并打印“Inserted!”。記住驾霜,在一個(gè)普通應(yīng)用中案训,你應(yīng)該總是檢查 t
變量中是否有錯(cuò)誤信息。
添加多個(gè) document
要添加多個(gè) documents粪糙,你可以使用 insertMany()
方法强霎。
接下來的例子會(huì)添多個(gè)document,document形式如下:
{ "i" : value }
循環(huán)創(chuàng)建多個(gè) documents 蓉冈。
List<Document> documents = new ArrayList<Document>();
for (int i = 0; i < 100; i++) {
documents.add(new Document("i", i));
}
要插入多個(gè) document 到 collection城舞,傳遞 documents 列表到 insertMany()
方法.
collection.insertMany(documents, new SingleResultCallback<Void>() {
@Override
public void onResult(final Void result, final Throwable t) {
System.out.println("Documents inserted!");
}
});
統(tǒng)計(jì)一個(gè) collection的document數(shù)量
既然前面的多個(gè)例子中我們已經(jīng)插入了 101 個(gè) document,我們可以檢查一下插入數(shù)量寞酿,使用 count()
方法家夺。下面的代碼應(yīng)該打印 101
。
collection.count(
new SingleResultCallback<Long>() {
@Override
public void onResult(final Long count, final Throwable t) {
System.out.println(count);
}
});
查詢 collection
使用 find()
方法來查詢 collection伐弹。
在一個(gè) collection 中找到第一個(gè) document
要獲得 collection 中的第一個(gè) document 拉馋,需要調(diào)用 first()
方法。collection.find().first()
返回第一個(gè) document 或 null 值惨好,而不是一個(gè)游標(biāo)煌茴。這種查詢適用于匹配一個(gè)單一的 document,,或你僅對(duì)第一個(gè) document 有興趣昧狮。
注意
有時(shí)你需要多次使用相同或相似的回調(diào)方法景馁。在這種情況下,合理的做法是DRY(不要重復(fù)自己):把回調(diào)保存為一個(gè)具體的類或分配給一個(gè)變量逗鸣。
SingleResultCallback<Document> printDocument = new SingleResultCallback<Document>() {
@Override
public void onResult(final Document document, final Throwable t) {
System.out.println(document.toJson());
}
};
下面的例子傳遞 printDocument
回調(diào)給 first
方法:
collection.find().first(printDocument);
范例會(huì)打印下面的 document:
{ "_id" : { "$oid" : "551582c558c7b4fbacf16735" },
"name" : "MongoDB", "type" : "database", "count" : 1,
"info" : { "x" : 203, "y" : 102 } }
注意
_id
元素會(huì)被MongoDB動(dòng)態(tài)的添加到你的 document 上,并且值也會(huì)與展示的不同绰精∪鲨担“_” 和 “$”開頭的域是MongoDB 預(yù)留給內(nèi)部使用的。
遍歷查找一個(gè)collection中所有的 document
要檢索 collection 中所有的 document笨使,需要使用 find()
方法卿樱。find() 方法返回一個(gè) FindIterable
實(shí)例,它提供了一個(gè)接口來鏈接和控制查找操作硫椰。使用 forEach()
方法可以提供一個(gè) Block
作用于每個(gè) document 并且迭代結(jié)束時(shí)執(zhí)行回調(diào)一次繁调。下面的代碼遍歷 collection 中所有的 document 并逐一打印,最后打印 “Operation Finished!”靶草。
Block<Document> printDocumentBlock = new Block<Document>() {
@Override
public void apply(final Document document) {
System.out.println(document.toJson());
}
};
SingleResultCallback<Void> callbackWhenFinished = new SingleResultCallback<Void>() {
@Override
public void onResult(final Void result, final Throwable t) {
System.out.println("Operation Finished!");
}
};
collection.find().forEach(printDocumentBlock, callbackWhenFinished);
通過查詢條件獲得一個(gè) document
我們可以創(chuàng)建一個(gè)過濾器傳遞給 find() 方法蹄胰,以獲得我們 collection 中的一組子集。例如奕翔,如果我們想查找 key為“i” 裕寨,value為71 的 document,我們要按下面的方法做(重用 printDocument
回調(diào))。
import static com.mongodb.client.model.Filters.*;
collection.find(eq("i", 71)).first(printDocument);
最終會(huì)只印一個(gè) document:
{ "_id" : { "$oid" : "5515836e58c7b4fbc756320b" }, "i" : 71 }
重要
請(qǐng)使用 Filters
宾袜、Sorts
捻艳、Projections
和 Updates
API手冊(cè)來找到簡(jiǎn)單、清晰的方法構(gòu)建查詢庆猫。
通過查詢獲得一組 documents
我們可以使用查詢來從我們的 collection 中獲得一組 document 集合认轨。例如,如果我們想獲得所有 key 為“i”月培,value 大于50 的 document 嘁字,我們應(yīng)該按下面方式做(重用 printDocumentBlock
阻塞和 callbackWhenFinished
回調(diào)):
// 使用范圍查詢獲取子集
collection.find(gt("i", 50)).forEach(printDocumentBlock, callbackWhenFinished);
范例應(yīng)該會(huì)打印所有 i > 50
的document。
我們也可以增加上限范圍节视,如 50 < i <= 100
:
collection.find(and(gt("i", 50), lte("i", 100))).forEach(printDocumentBlock, callbackWhenFinished);
document 排序
我們可以對(duì) document 進(jìn)行排序拳锚。通過在 FindIterable
上調(diào)用 sort()
方法,我們可以在一個(gè)查詢上進(jìn)行一次排序寻行。
下面的例子中霍掺,我們使用 exists()
和 降序排序 descending("i")
來為我們的 document 排序。
collection.find(exists("i")).sort(descending("i")).first(printDocument);
投射域
有時(shí)我們不需要將所有的數(shù)據(jù)都存在一個(gè) document 中拌蜘。Projections 可以用來為查詢操作構(gòu)建投射參數(shù)并限制返回的字段杆烁。
下面的例子中,我們會(huì)對(duì)collection進(jìn)行排序简卧,排除 _id
字段兔魂,并輸出第一個(gè)匹配的 document。
collection.find().projection(excludeId()).first(printDocument);
聚合
有時(shí)举娩,我們需要將存儲(chǔ)在 MongoDB 中的數(shù)據(jù)聚合析校。 Aggregates
支持對(duì)每種類型的聚合階段進(jìn)行構(gòu)建。
下面的例子铜涉,我們執(zhí)行一個(gè)兩步驟的轉(zhuǎn)換來計(jì)算 i * 10
的值智玻。首先我們使用 Aggregates.match
查找所有 i > 0
的document 。接著芙代,我們使用 Aggregates.project
結(jié)合 $multiply
操作來計(jì)算 “ITimes10
” 的值吊奢。
collection.aggregate(asList(
match(gt("i", 0)),
project(Document.parse("{ITimes10: {$multiply: ['$i', 10]}}")))
).forEach(printDocumentBlock, callbackWhenFinished);
For $group
operations use the Accumulators
helper for any accumulator operations.
對(duì)于 $group
操作使用 Accumulators
來處理任何 累加操作 。
下面的例子中纹烹,我們使用 Aggregates.group
結(jié)合 Accumulators.sum
來累加所有 i
的和页滚。
collection.aggregate(singletonList(group(null, sum("total", "$i")))).first(printDocument);
注意
當(dāng)前,還沒有專門用于 聚合表達(dá)式 的工具類铺呵」郏可以使用 Document.parse()
來快速構(gòu)建來自于JSON的聚合表達(dá)式。
更新 document
MongoDB 支持許多的 更新操作 陪蜻。
要更新至多一個(gè) document (可能沒有匹配的document)邦马,使用 updateOne
方法指定過濾器并更新 document 。這里,我們使用 Updates.set
來更新匹配過濾器 i
等于 10 的第一個(gè) document 并設(shè)置 i
的值為 110滋将。
collection.updateOne(eq("i", 10), set("i", 110),
new SingleResultCallback<UpdateResult>() {
@Override
public void onResult(final UpdateResult result, final Throwable t) {
System.out.println(result.getModifiedCount());
}
});
使用 updateMany
方法可以更新所有匹配過濾器的 document 邻悬。這里我們使用 Updates.inc
來為所有 i
小于 100 的document 增加 100 。
collection.updateMany(lt("i", 100), inc("i", 100),
new SingleResultCallback<UpdateResult>() {
@Override
public void onResult(final UpdateResult result, final Throwable t) {
System.out.println(result.getModifiedCount());
}
});
更新方法返回一個(gè) UpdateResult
随闽,其中包含了操作的信息(被修改的 document 的數(shù)量)父丰。
刪除 document
要?jiǎng)h除至多一個(gè) document (可能沒有匹配的document)可以使用 deleteOne
方法。
collection.deleteOne(eq("i", 110), new SingleResultCallback<DeleteResult>() {
@Override
public void onResult(final DeleteResult result, final Throwable t) {
System.out.println(result.getDeletedCount());
}
});
使用 deleteMany
方法可以刪除所有匹配過濾器的 document 掘宪。這里我們刪除所有 i
大于等于的 document蛾扇。
collection.deleteMany(gte("i", 100), new SingleResultCallback<DeleteResult>() {
@Override
public void onResult(final DeleteResult result, final Throwable t) {
System.out.println(result.getDeletedCount());
}
});
刪除方法返回一個(gè) DeleteResult
,其中包含了操作的信息(被刪除的 document 的數(shù)量)魏滚。
批量操作
批量操作允許批量的執(zhí)行 插入镀首、更新、刪除操作鼠次。批量操作有兩種類型:
-
有序的批量操作
有序的執(zhí)行所有操作并在第一個(gè)寫操作的錯(cuò)誤處報(bào)告錯(cuò)誤更哄。
-
無序的批量操作
執(zhí)行所有的操作并報(bào)告任何錯(cuò)誤。
無序的批量操作不保證執(zhí)行順序腥寇。
我們來圍觀一下兩個(gè)分別使用有序和無序操作的簡(jiǎn)單例子:
SingleResultCallback<BulkWriteResult> printBatchResult = new SingleResultCallback<BulkWriteResult>() {
@Override
public void onResult(final BulkWriteResult result, final Throwable t) {
System.out.println(result);
}
};
// 2. 有序批量操作
collection.bulkWrite(
Arrays.asList(new InsertOneModel<>(new Document("_id", 4)),
new InsertOneModel<>(new Document("_id", 5)),
new InsertOneModel<>(new Document("_id", 6)),
new UpdateOneModel<>(new Document("_id", 1),
new Document("$set", new Document("x", 2))),
new DeleteOneModel<>(new Document("_id", 2)),
new ReplaceOneModel<>(new Document("_id", 3),
new Document("_id", 3).append("x", 4))),
printBatchResult
);
// 2. 無序批量操作
collection.bulkWrite(
Arrays.asList(new InsertOneModel<>(new Document("_id", 4)),
new InsertOneModel<>(new Document("_id", 5)),
new InsertOneModel<>(new Document("_id", 6)),
new UpdateOneModel<>(new Document("_id", 1),
new Document("$set", new Document("x", 2))),
new DeleteOneModel<>(new Document("_id", 2)),
new ReplaceOneModel<>(new Document("_id", 3),
new Document("_id", 3).append("x", 4))),
new BulkWriteOptions().ordered(false),
printBatchResult
);
重要
不推薦在pre-2.6的MongoDB 服務(wù)器上使用 bulkWrite
方法成翩。因?yàn)檫@是第一個(gè)支持批量寫操作(插入、更新赦役、刪除)的服務(wù)器版本麻敌,它允許驅(qū)動(dòng)去實(shí)現(xiàn) BulkWriteResult
和 BulkWriteException
的語義。這個(gè)方法雖然仍然可以在pre-2.6服務(wù)器上工作掂摔,但是性能不好术羔,一次只能執(zhí)行一個(gè)寫操作。