最近項目在使用MongoDB作為圖片和文檔的存儲數據庫,為啥不直接存MySQL里,還要搭個MongoDB集群,麻不麻煩?
讓我們一探究竟,了解一下MongoDB的特點和基本用法。
三分鐘你將學會:
- MongoDB主要特征
- MongoDB優缺點,揚長避短
- 何時選擇MongoDB?為啥要用它?
- MongoDB與MySQL關鍵字對比
- 下載與安裝過程中一些常見的坑
- JAVA整合MongoDB,實現農民工增刪改查
一、基本概念
MongoDB是一款開源、跨平臺、分布式,具有大數據處理能力的文檔存儲數據庫。文檔數據庫MongoDB用于記錄文檔結構的數據,比如JSON、XML結構的數據。
二、MongoDB的主要特征
- 高性能。提供JSON、XML等可嵌入數據快速處理功能,提供文檔的索引功能,以提高查詢速度;
- 豐富的查詢語言。為數據聚合、結構文檔、地理空間提供豐富的查詢功能;
- 高可用性。提供自動故障轉移和數據冗余處理功能;
- 水平擴展能力。提供基于多服務器集群的分布式數據處理能力,具體處理時分「主從」和「權衡」(基于Hash自動推選)兩種處理模式;
- 支持多種存儲引擎。MongoDB提供多種存儲引擎,WiredTiger引擎、MMAPv1引擎是基于硬盤讀寫的存儲引擎,In-Memory引擎是基于內存的存儲引擎。
三、MongoDB的優缺點
1、優點
- Free-schema無模式文檔,適應非結構化數據存儲;
- 內置GridFS,支持大容量的存儲;
- 內置Sharding,分片簡單
- 弱一致性(最終一致),更能保證用戶的訪問速度;
- 查詢性能優越,對于千萬級別的文檔對象,差不多10個G,對有索引的ID的查詢不會比MySQL慢,而對非索引字段的查詢,則是完勝MySQL;
- 聚合框架,它支持典型幾種聚合操作 , 比如,Aggregate pipelien, Map-Reduce等;
- 支持自動故障恢復
2、缺點
- 太吃內存,快是有原因的,因為MongoDB把數據都放內存里了;
- 對事務的支持不夠友好;
- 占用空間過大;
- 對聯表查詢的支持不夠強大;
- 只有最終一致性,言外之意,就是可能造成數據的不一致,如果想要保持強一致性,必須在一個服務器處理所有的讀寫操作,坑;
- 復雜聚合操作通過mapreduce創建,速度慢
- Mongodb全局鎖機制也是個坑;
- 預分配模式會帶來的磁盤瓶頸;
- 刪除記錄時不會釋放空間,相當于邏輯刪除,這個真的坑;
- MongoDB到現在為止,好像還沒有太好用的客戶端工具。
四、何時選擇MongoDB?
為啥要用它?
MongoDB事務
MongoDB目前只支持單文檔事務,MongoDB暫時不適合需要復雜事務的場景。靈活的文檔模型JSON格式存儲最接近真實對象模型,對開發者友好,方便快速開發迭代,可用復制集滿足數據高可靠、高可用的需求,運維較為簡單、故障自動切換可擴展分片集群海量數據存儲。
多引擎支持各種強大的索引需求
支持地理位置索引
可用于構建各種O2O應用
文本索引解決搜索的需求
TTL索引解決歷史數據過期的需求
Gridfs解決文件存儲的需求
aggregation & mapreduce解決數據分析場景需求,可以自己寫查詢語句或腳本,將請求分發到 MongoDB 上完成。
具體的應用場景
傳統的關系型數據庫在解決三高問題上的力不從心。何為三高?
- High performance - 對數據庫高并發讀寫的需求。
- Huge Storage - 對海量數據的高效率存儲和訪問的需求。
- High Scalability && High Availability- 對數據庫的高可擴展性和高可用性的需求。
MongoDB可以完美解決三高問題。
以下是幾個實際的應用案例:
1)游戲場景
使用MongoDB存儲游戲用戶信息、裝備、積分等,直接以內嵌文檔的形式存儲,方便查詢、更新。
2)物流場景
使用MongoDB存儲訂單信息、訂單狀態、物流信息,訂單狀態在運送過程中飛速迭代、以MongoDB內嵌數組的形式來存儲,一次查詢就能將訂單所有的變更查出來,牛逼plus。
3)社交場景
使用MongoDB存儲用戶信息,朋友圈信息,通過地理位置索引實現附近的人、定位功能。
4)物聯網場景
使用MongoDB存儲設備信息、設備匯報的日志信息、并對這些信息進行多維度分析。
5)視頻直播
使用MongoDB存儲用戶信息、點贊互動信息。
選擇MongoDB的場景總結
- 數據量大
- 讀寫操作頻繁
- 數據價值較低,對事務要求不高
五、MongoDB與MySQL關鍵字對比
關鍵字對比
集合相當于MySQL中的表
集合就是一組文檔。可以看作是具有動態模式的表。
集合具有動態模式的特性。這意味著一個集合中的文檔可以具有任意數量的不同形態。
但是,將不同類型的文檔存放在一個集合中會出現很多問題:
- 文檔中可以存放任意類型的變量,但是,這里不建議將不同類型的文檔保存在同一個集合中,開發人員需要確保每個查詢只返回特定模式的文檔,或者確保執行查詢的應用程序代碼可以處理不同類型的文檔;
- 獲取集合列表比提取集合中的文檔類型列表要快得多,減少磁盤查找次數;
- 相同類型的文檔存放在同一個集合中可以實現數據的局部性,對于集合,讓使用者見文知意;
- 集合中只存放單一類型的文檔,可以更高效地對集合進行索引。
集合的命名
- 集合名稱中不能是空字符串;
- 集合名稱不能包含(空字符),因為這個字符用于表示一個集合名稱的結束;
- 集合名稱不能以system.開頭,該前綴是為內部集合保留的。
- 集合名稱不能有$,只能在某些特定情況下使用。通常情況下,可以認為這兩個字符是MongoDB的保留字符,如果使用不當,那么驅動程序將無法正常工作。
文檔相當于MySQL中的行
文檔是MongoDB中的基本數據單元,相當于傳統關系型數據庫中的行,它是一組有序鍵值的集合。每個文檔都有一個特殊的鍵“_id”,其在所屬的集合中是唯一的。
文檔中的鍵是字符串類型。
鍵中不能含有(空字符)。這個字符用于表示一個鍵的結束。.和$是特殊字符,只能在某些特定情況下使用。通常情況下,可以認為這兩個字符是MongoDB的保留字符,如果使用不當,那么驅動程序將無法正常工作。
游標
數據庫會使用游標返回find的執行結果。游標的客戶端實現通常能夠在很大程度上對查詢的最終輸出進行控制。你可以限制結果的數量,跳過一些結果,按任意方向的任意鍵組合對結果進行排序,以及執行許多其他功能強大的操作。
通過cursor.hasNext()檢查是否還有其它結果,通過cursor.next()用來對其進行獲取。
調用find()時,shell并不會立即查詢數據庫,而是等到真正開始請求結果時才發送查詢,這樣可以在執行之前給查詢附加額外的選項。cursor對象的大多數方法會返回游標本身,這樣就可以按照任意順序將選項鏈接起來了。
在使用db.users.find();查詢時,實際上查詢并沒有真正執行,只是在構造查詢,執行cursor.hasNext(),查詢才會發往服務器端。shell會立刻獲取前100個結果或者前4MB的數據(兩者之中的較小者),這樣下次調用next或者hasNext時就不必再次連接服務器去獲取結果了。在客戶端遍歷完第一組結果后,shell會再次連接數據庫,使用getMore請求更多的結果。getMore請求包含一個游標的標識符,它會向數據庫詢問是否還有更多的結果,如果有則返回下一批結果。這個過程會一直持續,直到游標耗盡或者結果被全部返回。
游標的生命周期
在服務器端,游標會占用內存和資源。一旦游標遍歷完結果之后,或者客戶端發送一條消息要求終止,數據庫就可以釋放它正在使用的資源。
何時銷毀游標:
- 當游標遍歷完匹配的結果時,它會消除自身;
- 當游標超出客戶端的作用域時,驅動程序會向數據庫發送一條特殊的消息,讓數據庫終止該游標;
- 如果10分鐘沒有被使用的話,數據庫游標也將自動銷毀。
六、下載與安裝過程中一些常見的坑
1、下載地址:
https://www.mongodb.com/try/download/community2
- 配置環境變量D:Program FilesMongoDBServer5.0bin
- 在bin目錄下,重新打開一個窗口,D:Program FilesMongoDBServer5.0bin,打開cmd,輸入MongoDB
- 如果msi方式失敗,可以下載zip文件進行安裝
下載zip文件,解壓,在bin同級目錄下建data文件夾,在data下建一個db文件夾,存儲MongoDB數據。
在bin文件夾下執行cmd,執行mongod --dbpath D:Program Filesmongodbdatadb命令;
再在data目錄下,建一個logs文件夾,存放MongoDB日志。
在mongodb/bin目錄下,建一個mongod.cfg文件,寫入
systemLog:
destination: file
logAppend: true
path: D:Program Filesmongodbdatalogsmongod.log
storage:
dbPath: D:Program Filesmongodbdatadb
執行mongod --config "D:Program Filesmongodbbinmongod.cfg" --install 命令,安裝MongoDB。
通過mongod --version檢查MongoDB版本。
D:Program Filesmongodbbin>mongod --version
db version v5.0.14
Build Info: {
"version": "5.0.14",
"gitVersion": "1b3b0073a0b436a8a502b612f24fb2bd572772e5",
"modules": [],
"allocator": "tcmalloc",
"environment": {
"distmod": "windows",
"distarch": "x86_64",
"target_arch": "x86_64"
}
}
2、mongodb由于目標計算機積極拒絕,無法連接
突然間,mongodb無法連接了?mongod.exe --dbpath "D:Program Filesmongodbdata完美解決。
注意一點,在重新啟動時,執行mongod.exe --dbpath "D:Program Filesmongodbdata的窗口不要關閉。
3、由于找不到vcruntime140_1.dll,無法繼續執行代碼
下載vcruntime140_1.dll文件;
將vcruntime140_1.dll文件拷貝到C:WindowsSystem32即可。
七、Java整合MongoDB,實現農民工增刪改查
加入POM
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.8.2</version>
</dependency>
MongoDBUtil工具類
package com.example.demo.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.bson.Document;
import org.bson.conversions.Bson;
import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
public class MongoDBUtil {
private static MongoClient mongoClient;
private static MongoClient mongoClientIdentify;
/**
* 不通過認證獲取連接數據庫對象
*/
public static MongoDatabase getNoIdentifyConnect(String host, int port, String dbaseName) {
// 連接mongodb服務
MongoDBUtil.mongoClient = new MongoClient(host, port);
// 連接數據庫
MongoDatabase mongoDatabase = MongoDBUtil.mongoClient.getDatabase(dbaseName);
// 返回連接數據庫對象
return mongoDatabase;
}
/**
* 通過連接認證獲取MongoDB連接
*/
public static MongoDatabase getIdentifyConnect(String host, int port, String dbaseName, String userName, String password) {
List<ServerAddress> adds = new ArrayList<ServerAddress>();
ServerAddress serverAddress = new ServerAddress(host, port);
adds.add(serverAddress);
List<MongoCredential> credentials = new ArrayList<>();
MongoCredential mongoCredential = MongoCredential.createScramSha1Credential(userName, dbaseName, password.toCharArray());
credentials.add(mongoCredential);
// 通過連接認證獲取MongoDB連接
MongoDBUtil.mongoClientIdentify = new MongoClient(adds, credentials);
MongoDatabase mongoDatabase = MongoDBUtil.mongoClientIdentify.getDatabase(dbaseName);
return mongoDatabase;
}
/**
* 關閉連接
*/
public static void closeNoIdentifyConnect () {
MongoDBUtil.mongoClient.close();
}
/**
* 關閉連接
*/
public static void closeIdentifyConnect () {
MongoDBUtil.mongoClientIdentify.close();
}
/**
* 插入一個文檔
*/
public static void insertOne (Map<String, Object> data, MongoDatabase mongoDatabase, String col) {
//獲取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//創建文檔
Document document = new Document();
for (Map.Entry<String, Object> m : data.entrySet()) {
document.append(m.getKey(), m.getValue()).append(m.getKey(), m.getValue());
}
//插入一個文檔
collection.insertOne(document);
}
/**
* 插入多個文檔
*/
public static void insertMany (List<Map<String, Object>> listData, MongoDatabase mongoDatabase, String col) {
//獲取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//要插入的數據
List<Document> list = new ArrayList<>();
for (Map<String, Object> data : listData) {
//創建文檔
Document document = new Document();
for (Map.Entry<String, Object> m : data.entrySet()) {
document.append(m.getKey(), m.getValue());
}
list.add(document);
}
//插入多個文檔
collection.insertMany(list);
}
/**
* 刪除匹配到的第一個文檔
*/
public static void delectOne (String col, String key, Object value, MongoDatabase mongoDatabase) {
//獲取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//申明刪除條件
Bson filter = Filters.eq(key, value);
//刪除與篩選器匹配的單個文檔
collection.deleteOne(filter);
}
/**
* 刪除匹配的所有文檔
*/
public static void deleteMany (String col, String key, Object value, MongoDatabase mongoDatabase) {
//獲取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//申明刪除條件
Bson filter = Filters.eq(key, value);
//刪除與篩選器匹配的所有文檔
collection.deleteMany(filter);
}
/**
* 刪除集合中所有文檔
*/
public static void deleteAllDocument(String col, MongoDatabase mongoDatabase) {
//獲取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
collection.deleteMany(new Document());
}
/**
* 刪除文檔和集合。
*/
public static void deleteAllCollection(String col, MongoDatabase mongoDatabase) {
//獲取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
collection.drop();
}
/**
* 修改單個文檔,修改過濾器篩選出的第一個文檔
* @param col 修改的集合
* @param key 修改條件的鍵
* @param value 修改條件的值
* @param eqKey 要修改的鍵,如果eqKey不存在,則新增記錄
* @param eqValue 要修改的值
* @param mongoDatabase 連接數據庫對象
*/
public static void updateOne (String col, String key, Object value,String eqKey, Object eqValue, MongoDatabase mongoDatabase) {
//獲取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//修改過濾器
Bson filter = Filters.eq(key, value);
//指定修改的更新文檔
Document document = new Document("$set", new Document(eqKey, eqValue));
//修改單個文檔
collection.updateOne(filter, document);
}
/**
* 修改多個文檔
* @param col 修改的集合
* @param key 修改條件的鍵
* @param value 修改條件的值
* @param eqKey 要修改的鍵,如果eqKey不存在,則新增記錄
* @param eqValue 要修改的值
* @param mongoDatabase 連接數據庫對象
*/
public static void updateMany (String col, String key, Object value, String eqKey, Object eqValue, MongoDatabase mongoDatabase) {
//獲取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//修改過濾器
Bson filter = Filters.eq(key, value);
//指定修改的更新文檔
Document document = new Document("$set", new Document(eqKey, eqValue));
//修改多個文檔
collection.updateMany(filter, document);
}
/**
* 查找集合中的所有文檔
*/
public static MongoCursor<Document> find (String col, MongoDatabase mongoDatabase) {
//獲取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//查找集合中的所有文檔
FindIterable<Document> findIterable = collection.find();
MongoCursor<Document> cursorIterator = findIterable.iterator();
return cursorIterator;
}
/**
* 按條件查找集合中文檔
*/
public static MongoCursor<Document> Filterfind (String col,String key, Object value, MongoDatabase mongoDatabase) {
//獲取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//指定查詢過濾器
Bson filter = Filters.eq(key, value);
//指定查詢過濾器查詢
FindIterable<Document> findIterable = collection.find(filter);
MongoCursor<Document> cursorIterator = findIterable.iterator();
return cursorIterator;
}
}
測試類
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version></dependency>
package com.example.demo.utils;
import com.mongodb.client.MongoCursor;import com.mongodb.client.MongoDatabase;import org.bson.Document;import org.junit.Test;
import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;
public class MongoDBTest {
// 獲取數據庫連接對象 MongoDatabase mongoDatabase = MongoDBUtil.getNoIdentifyConnect("127.0.0.1", 27017, "test");
@Test public void insertOne() { Map<String, Object> map = new HashMap<String, Object>(); map.put("姓名", "哪吒編程"); map.put("性別", "男"); map.put("年齡", 18); MongoDBUtil.insertOne(map, mongoDatabase, "worker"); MongoDBUtil.closeNoIdentifyConnect(); }
@Test public void insertMany() { Map<String, Object> map1 = new HashMap<String, Object>(); map1.put("姓名", "哪吒編程2"); map1.put("性別", "男"); map1.put("年齡", 18); Map<String, Object> map2 = new HashMap<String, Object>(); map2.put("姓名", "妲己"); map2.put("性別", "女"); map2.put("年齡", 18); List<Map<String, Object>> listData = new ArrayList<>(); listData.add(map1); listData.add(map2); MongoDBUtil.insertMany(listData, mongoDatabase, "worker"); MongoDBUtil.closeNoIdentifyConnect(); }
@Test public void delectOne() { MongoDBUtil.delectOne("worker", "姓名", "妲己", mongoDatabase);
MongoDBUtil.closeNoIdentifyConnect(); }
@Test public void deleteMany() {
MongoDBUtil.deleteMany("worker", "姓名", "哪吒編程", mongoDatabase); MongoDBUtil.deleteMany("worker", "姓名", "妲己", mongoDatabase);
MongoDBUtil.closeNoIdentifyConnect(); }
@Test public void deleteAllDocument() { MongoDBUtil.deleteAllDocument("worker", mongoDatabase);
MongoDBUtil.closeNoIdentifyConnect(); }
@Test public void deleteAllCollection() {
MongoDBUtil.deleteAllCollection("worker", mongoDatabase);
MongoDBUtil.closeNoIdentifyConnect(); }
@Test public void updateOne() {
MongoDBUtil.updateOne("worker", "姓名", "哪吒編程2","姓名", "哪吒編程", mongoDatabase);
MongoDBUtil.closeNoIdentifyConnect(); }
@Test public void updateMany() {
MongoDBUtil.updateMany("worker", "姓名", "哪吒編程2","姓名", "哪吒編程", mongoDatabase);
MongoDBUtil.closeNoIdentifyConnect(); }
@Test public void find() { MongoCursor<Document> mongoCursor = MongoDBUtil.find("worker", mongoDatabase);
while (mongoCursor.hasNext()) { Document document = mongoCursor.next(); System.out.println(document + " size: " + document.size()); } MongoDBUtil.closeNoIdentifyConnect(); }
@Test public void filterfind() { MongoCursor<Document> mongoCursor = MongoDBUtil.Filterfind("worker", "姓名", "哪吒編程", mongoDatabase);
while (mongoCursor.hasNext()) { Document document = mongoCursor.next(); System.out.println(document + " size: " + document.size()); } MongoDBUtil.closeNoIdentifyConnect(); }}
作者丨哪吒
來源丨公眾號:哪吒編程(ID:gh_61b183bcf690)