雙刃劍MongoDB的學習和避坑

itdragon發表於2018-03-07

雙刃劍MongoDB的學習和避坑

MongoDB 是一把雙刃劍,它對資料結構的要求並不高。資料通過key-value的形式儲存,而value的值可以是字串,也可以是文件。所以我們在使用的過程中非常方便。正是這種方便給我們埋下了一顆顆地雷。當內嵌的文件太深,或者內嵌文件有相同的屬性名。你會被炸得很慘。本章節通過 MongoDB簡介,Shell程式設計,SpringBoot整合MongoDB,工作中注意事項,四個方面介紹MongoDB的使用。讓你輕鬆入門,輕鬆避坑。還在等什麼,趕快來學習吧!

技術:MongoDB,SpringBoot,SpringDataMongoDB
說明:本章重點介紹MongoDB的使用,對非關係型資料庫的介紹會比較簡單。完整程式碼和相關sql請移步github,ths!
原始碼:https://github.com/ITDragonBl…

MongoDB 簡介

MongoDB 是非關係型資料庫中,最接近關係型資料庫的,文件型資料庫。它支援的查詢功能非常強大。
MongoDB 是為快速開發網際網路Web應用而設計的資料庫系統。他的資料模型是面向文件的,這種文件是一種類似於JSON的結構,準確來說是一種支援二進位制的BSON(Binary JSON)結構。

非關係性資料庫

非關係性資料庫 也被稱為 NoSQL(Not only sql),主要有四大類:鍵值儲存資料庫、列儲存資料庫、文件型資料庫、圖形資料庫。之前介紹的Redis屬於鍵值儲存資料庫。

關係與非關係型資料庫

關係型資料庫的優點:
1 支援事務處理,事務特性:原子性、一致性、隔離性、永續性。
2 資料結構清晰,便於理解,可讀性高。
3 使用方便,有標準的sql語法。

關係型資料庫的缺點:
1 讀寫效能相對較差,為保證事務的一致性,需要一定的開銷。在高併發下表現的尤為突出。
2 表結構固定,不易於表後期的擴充套件,所以前期對錶的設計要求較高。

非關係型資料庫的優點:
1 讀寫效能高,沒有保障資料的一致性。
2 表結構靈活,表結構並不是固定的,通過key-value儲存資料,value又可以儲存其他格式的資料。

兩者的優缺點其實是向反的,一件事物不會憑空出現,都是在原有的基礎上做了補充和優化,兩者的側重點各有不同。就像MySQL保障了資料的一致性,卻影響了讀寫的效能。MongoDB放棄資料的強一致性,保障了讀寫的效率。在合適的場景使用合適的資料庫,是需要我們考慮的。
1 對於需要高度事務特性的系統,比如和錢有關的,銀行系統,金融系統。我們要考慮使用關係型資料庫,確保資料的一致性和永續性。
2 對於那些資料並不是很重要,訪問量又很大的系統,比如電商平臺的商品資訊。我們可以使用非關係型資料庫來做快取,充分提高了系統查詢的效能。

這裡對銀行和金融我想抱怨兩句:
第一:投資理財千萬不要選擇小平臺金融公司,收益再高都是虛假的,多半都是圈錢跑路的,錢的教訓。
第二:某些銀行APP顯示的金額不是實時的。16年某生銀行卡轉入40萬,但在我的總資產介面並沒有轉入的金額,嚇得我一身冷汗。顫抖著雙手給客服打了幾個電話才知道,某生銀行APP的總資產介面資料是統計前一天的。直到第二天,金額才顯示正確。從此我再也沒有用某生的銀行卡。某商的信用卡也是一樣,還了錢金額並沒有減下來。不知道現在有沒有改。

有在銀行工作的朋友,能否告訴我這樣設計的原因是啥?難道使用者體驗不重要?還是要體現客服的價值?反正,這鍋我們程式設計師不背。

Mongodb Shell 程式設計

查詢資料

Mongodb的查詢功能十分強大,有find() 和 findOne()。支援的查詢條件有:$lt、$lte、$gt、$gte、$ne、$or、$in、$nin、$not、$exists、$and、正規表示式等。

  • db.collection.find() 根據查詢條件返回所有文件
  • db.collection.findOne() 根據查詢條件返回第一個文件

查詢建議:
1 查詢所有資料,建議使用分頁查詢。
2 查詢key建議用引號,物件的屬性可以省略引號,內嵌的物件屬性不能省略。比如下面的name可以省略,但address.province則不能。
3 儘量少用$or, $in 查詢,效率很低。

// 查詢所有(不推薦,一般使用分頁查詢)
db.itdragonuser.find();
{"_id":ObjectId("5a9bbefa2f3fdfdf540a1be7"),"name":"ITDragon","age":25,"address":{"province":"廣東省","city":"深圳"},"ability":["JAVA"]}
// 等於查詢
db.itdragonuser.find({"name":"ITDragon"});
// 模糊查詢
db.itdragonuser.find({"name":/ITDragon/});
// 或者查詢
db.itdragonuser.find({$or:[{"address.province":"湖北"},{"address.province":"湖南"}]});
// 包含查詢(包含了JAVA或者HTML)
db.itdragonuser.find({"ability":{$in:["JAVA","HTML"]}});
// 不包含查詢(JAVA和HTML都不包含)
db.itdragonuser.find({"ability":{$nin:["JAVA","HTML"]}});
// 範圍查詢$gt , $lt , $gte , $lte , $ne
db.itdragonuser.find({"age":{$gt:25}});
// 正規表示式查詢(查詢以WeiXin結尾的資料)
db.itdragonuser.find({"name":/WeiXin$/});
// 按照條件統計資料
db.itdragonuser.count({"name":/ITDragon/});
// 過濾重複內容(列印不重複的name值)
db.itdragonuser.distinct("name");
// sort:排序(1表示升序 -1表示降序),skip:跳過指定數量,limit:每頁查詢數量
db.itdragonuser.find().sort({"age":1}).skip(2).limit(3);
// 欄位投影,(0表示不顯示,1表示顯示)
db.itdragonuser.find({},{_id:0,name:1,address:1,aliblity:1});

插入資料

插入資料比較簡單,insert() 可以向集合插入一個或多個文件,而insertOne() 和 insertMany() 細化了insert() 方法,語法是一樣的,命名規則上更清晰。

  • db.collection.insert() 可以向集合中插入一個或多個文件
  • db.collection.insertOne() 向集合中插入一個文件
  • db.collection.insertMany()向集合中插入多個文件

插入建議:
1 插入資料不能破壞原有的資料結構,造成不必要的麻煩。
2 批量插入資料,儘量一次執行多個文件,而不是多個文件執行多次方法。

// 插入一條資料,型別有字串,數字,物件,集合
db.itdragonuser.insert({"name":"ITDragon","age":24,"address":{"province":"廣東","city":"深圳"},"ability":["JAVA","HTML"]})
// 插入多條資料
db.itdragonuser.insert([
{"name":"ITDragon","age":24,"address":{"province":"廣東","city":"深圳"},"ability":["JAVA","HTML"]},
{"name":"ITDragonGit","age":24,"address":{"province":"湖北","city":"武漢"},"ability":["JAVA","HTML","GIT"]}
])

更新資料

更新資料時,需要確保value的資料結構,是字串,是集合,還是物件,不能破壞原有的資料結構。儘量使用修改器來幫忙完成操作。

  • db.collection.update() 可以修改、替換集合中的一個或多個文件,預設修改第一個,若要修改多個,則需要使用multi:true
  • db.collection.updateOne() 修改集合中的一個文件
  • db.collection.updateMany() 修改集合中的多個文件
  • db.collection.replaceOne() 替換集合中的一個文件

常用的修改器:
$inc : 數值型別屬性自增
$set : 用來修改文件中的指定屬性
$unset : 用來刪除文件的指定屬性
$push : 向陣列屬性新增資料
$addToSet : 向陣列新增不重複的資料

更新建議:
1 更新資料不能破壞原有的資料結構。
2 正確使用修改器完成更新操作。

// 更新字串屬性
db.itdragonuser.update({"name":"ITDragonGit"},{$set:{"name":"ITDragon"}});
// 更新物件屬性
db.itdragonuser.update({"name":"ITDragon"},{$set:{"address.province":"廣東省"}});
// 更新集合屬性
db.itdragonuser.update({"name":"ITDragon"},{$push:{"ability":"MONGODB"}});
// 批量更新屬性
db.itdragonuser.updateMany({"name":"ITDragon"},{$set:{"age":25}});
// 批量更新屬性,加引數multi:true
db.itdragonuser.update({"name":"ITDragon"},{$set:{"age":25}},{multi:true});

刪除資料

刪除資料是一個非常謹慎的操作,實際開發中不會物理刪除資料,只是邏輯刪除。方便資料恢復和大資料分析。這裡只簡單介紹。

  • db.collection.remove() 刪除集合中的一個或多個文件(預設刪除多個)
  • db.collection.deleteOne() 刪除集合中的一個文件
  • db.collection.deleteMany() 刪除集合中的多個文件

SpringBoot MongoDB 整合

如果你覺得Spring整合MongoDB略顯麻煩,那SpringBoot整合MongoDB就是你的福音。SpringBoot旨在零配置,只需簡單的兩個步驟即可。
第一步:在pom.xml檔案中新增spring-boot-starter-data-mongodb

<dependency>    <!-- 新增對mongodb的支援 -->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

第二步:在application.properties檔案中配置MongoDB資料庫連結地址。
連結MongoDB資料庫地址規則:spring.data.mongodb.uri=mongodb://account:password@ip:port/database
其中 account和password方便是連結資料庫的賬號和密碼。而database是需要連結的資料庫地址

# 沒有賬號密碼可以簡寫
spring.data.mongodb.uri=mongodb://localhost:27017/itdragonstu

Spring Data MongoDB 程式設計

Spring Data給我們提供了MongoTemplate類,極大的方便了我們的工作,但是若每個實體類都基於MongoTemplate重寫一套CRUD的實現類,似乎顯得有些笨重。於是我們可以將其簡單的封裝一下。步驟如下

第一步:建立使用者實體類,其資料庫表名就是類名首字母小寫。
第二步:封裝MongoTemplate類,實現增刪改查,分頁,排序,主鍵自增等常用功能。
第三步:建立封裝類的Bean管理類,針對不同的實體類,需要配置不同的bean。
第四步:建立測試類,測試:註冊,更新,分頁,排序,查詢使用者功能。

建立使用者實體類

使用者實體類有五個欄位,除了主鍵ID,其他四個分別代表四個常用的型別(字串,數字,物件,集合)。為了簡化開發,實體類建議不實用@Document註解重新命名User在MongoDB資料庫中的表名。
省略get/set方法和toString方法

import java.io.Serializable;
import java.util.ArrayList;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
/**
 * 使用者實體類
 * @author itdragon
 */
//@Document(collection = "itdragon_user")  如果為了程式碼的通用性,建議不要使用
public class User implements Serializable{
    
    private static final long serialVersionUID = 1L;
    @Id
    private Long id;
    private String name;
    private Integer age;
    private Address address;
    private ArrayList ability;
}

public class Address{
    private Long id;
    private String province;
    private String city;
}

封裝MongoTemplate類

SpringData提供的MongoTemplate類,極大的方便我們操作MongoDB資料庫。可是它的很多方法都涉及到了Class,和CollectionName。針對不同的實體類,我們需要重複寫不同的方法。這裡,我們進一步封裝,實現程式碼的高可用。
實現的思路大致:將Class作為一個引數,在初始化MongoTemplate的封裝類時賦值。這裡有一個約束條件是:CollectionName是Class類名的首字母小寫。

import java.util.List;
import java.util.Map;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;

@Repository
@SuppressWarnings({"unchecked", "rawtypes"})
public class ITDragonMongoHelper {
    
    @Autowired(required = false)
    private MongoTemplate mongoTemplate; 
    private Class entityClass;        // 實體類
    private String collectionName;    // 資料庫表名
    private String orderAscField;    // 升序欄位
    private String orderDescField;    // 降序欄位
    
    private static final String ID = "id";
    private static final String MONGODB_ID = "_id";
    
    public ITDragonMongoHelper() {
    }

    public ITDragonMongoHelper(Class entityClass) {
        this.entityClass = entityClass;
        this.collectionName = _getCollectionName();
    }

    public ITDragonMongoHelper(Class entityClass, String collectionName) {
        this.entityClass = entityClass;
        this.collectionName = collectionName;
    }
    
    /**
     * @Title save
     * @Description 通過Map建立實體類
     * @param object Map,無需自帶ID
     * @return
     */
    public Boolean save(Map<String, Object> requestArgs) {
        try {
            Object object = getEntityClass().newInstance();
            if (null == requestArgs.get(ID)) {
                requestArgs.put(ID, getNextId());
            }
            BeanUtils.populate(object, requestArgs);
            saveOrUpdate(object);
        } catch (Exception e) {
            e.printStackTrace();
            return Boolean.valueOf(false);
        }
        return Boolean.valueOf(true);
    }
    
    /**
     * @Title save
     * @Description 通過物件建立實體類
     * @param object 實體類,需自帶ID
     * @return
     */
    public Boolean saveOrUpdate(Object object) {
        try {
            this.mongoTemplate.save(object, this.collectionName);
        } catch (Exception e) {
            e.printStackTrace();
            return Boolean.valueOf(false);
        }
        return Boolean.valueOf(true);
    }
    
    /**
     * @Title update
     * @Description 通過Map更新實體類具體欄位,可以減少更新出錯欄位,執行的銷率更高,需嚴格要求資料結構的正確性
     * @param requestArgs Map,需自帶ID, 形如:{id: idValue, name: nameValue, ....}
     * @return
     */
    public Boolean update(Map<String, Object> requestArgs) {
        Object id = requestArgs.get(ID);
        if (null == id) {
            return Boolean.valueOf(false);
        }
        try {
            Update updateObj = new Update();
            requestArgs.remove(ID);
            for (String key : requestArgs.keySet()) {
                updateObj.set(key, requestArgs.get(key));
            }
            findAndModify(Criteria.where(ID).is(id), updateObj);
        } catch (Exception e) {
            e.printStackTrace();
            return Boolean.valueOf(false);
        }
        return Boolean.valueOf(true);
    }
    
    /**
     * @Title find
     * @Description 根據查詢條件返回所有資料,不推薦
     * @param criteria 查詢條件
     * @return
     */
    public List find(Criteria criteria) {
        Query query = new Query(criteria);
        _sort(query);
        return this.mongoTemplate.find(query, this.entityClass, this.collectionName);
    }
    
    /**
     * @Title find
     * @Description 根據查詢條件返回指定數量資料
     * @param criteria 查詢條件
     * @param pageSize 查詢數量
     * @return
     */
    public List find(Criteria criteria, Integer pageSize) {
        Query query = new Query(criteria).limit(pageSize.intValue());
        _sort(query);
        return this.mongoTemplate.find(query, this.entityClass, this.collectionName);
    }

    /**
     * @Title find
     * @Description 根據查詢條件分頁返回指定數量資料
     * @param criteria 查詢條件
     * @param pageSize 查詢數量
     * @param pageNumber 當前頁數
     * @return
     */
    public List find(Criteria criteria, Integer pageSize, Integer pageNumber) {
        Query query = new Query(criteria).skip((pageNumber.intValue() - 1) * pageSize.intValue()).limit(pageSize.intValue());
        _sort(query);
        return this.mongoTemplate.find(query, this.entityClass, this.collectionName);
    }
    
    public Object findAndModify(Criteria criteria, Update update) {
        // 第一個引數是查詢條件,第二個引數是需要更新的欄位,第三個引數是需要更新的物件,第四個引數是MongoDB資料庫中的表名
        return this.mongoTemplate.findAndModify(new Query(criteria), update, this.entityClass, this.collectionName);
    }
    
    /**
     * @Title findById
     * @Description 通過ID查詢資料
     * @param id 實體類ID
     * @return
     */
    public Object findById(Object id) {
        return this.mongoTemplate.findById(id, this.entityClass, this.collectionName);
    }
    
    /**
     * @Title findOne
     * @Description 通過查詢條件返回一條資料
     * @param id 實體類ID
     * @return
     */
    public Object findOne(Criteria criteria) {
        Query query = new Query(criteria).limit(1);
        _sort(query);
        return this.mongoTemplate.findOne(query, this.entityClass, this.collectionName);
    }
    
    // id自增長
    public String getNextId() {
        return getNextId(getCollectionName());
    }

    public String getNextId(String seq_name) {
        String sequence_collection = "seq";
        String sequence_field = "seq";
        DBCollection seq = this.mongoTemplate.getCollection(sequence_collection);
        DBObject query = new BasicDBObject();
        query.put(MONGODB_ID, seq_name);
        DBObject change = new BasicDBObject(sequence_field, Integer.valueOf(1));
        DBObject update = new BasicDBObject("$inc", change);
        DBObject res = seq.findAndModify(query, new BasicDBObject(), new BasicDBObject(), false, update, true, true);
        return res.get(sequence_field).toString();
    }
    
    private void _sort(Query query) {
        if (null != this.orderAscField) {
            String[] fields = this.orderAscField.split(",");
            for (String field : fields) {
                if (ID.equals(field)) {
                    field = MONGODB_ID;
                }
                query.with(new Sort(Sort.Direction.ASC, new String[] { field }));
            }
        } else {
            if (null == this.orderDescField) {
                return;
            }
            String[] fields = this.orderDescField.split(",");
            for (String field : fields) {
                if (ID.equals(field)) {
                    field = MONGODB_ID;
                }
                query.with(new Sort(Sort.Direction.DESC, new String[] { field }));
            }
        }
    }
    
    // 獲取Mongodb資料庫中的表名,若表名不是實體類首字母小寫,則會影響後續操作
    private String _getCollectionName() {
        String className = this.entityClass.getName();
        Integer lastIndex = Integer.valueOf(className.lastIndexOf("."));
        className = className.substring(lastIndex.intValue() + 1);
        return StringUtils.uncapitalize(className);
    }
    
    public Class getEntityClass() {
        return entityClass;
    }
    public void setEntityClass(Class entityClass) {
        this.entityClass = entityClass;
    }
    public String getCollectionName() {
        return collectionName;
    }
    public void setCollectionName(String collectionName) {
        this.collectionName = collectionName;
    }
    public String getOrderAscField() {
        return orderAscField;
    }
    public void setOrderAscField(String orderAscField) {
        this.orderAscField = orderAscField;
    }
    public String getOrderDescField() {
        return orderDescField;
    }
    public void setOrderDescField(String orderDescField) {
        this.orderDescField = orderDescField;
    }
}

建立封裝類的Bean管理類

這裡用Bean註解修飾的方法名和測試類中ITDragonMongoHelper 的變數名要保持一致。這樣才能具體知道是哪個實體類的資料操作。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.itdragon.pojo.User;
import com.itdragon.repository.ITDragonMongoHelper;

/**
 * ITDragonMongoHelper的bean配置管理類 
 * @author itdragon
 */
@Configuration
public class MongodbBeansConfig {
    
    @Bean // 該方法名很重要
    public ITDragonMongoHelper userMongoHelper() {
        return new ITDragonMongoHelper(User.class);
    }

}

MongoDB的測試類

主要測試MongoDB儲存資料,更新字串,更新數值,更新物件(文件),更新集合,分頁查詢幾個常用方法。

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.test.context.junit4.SpringRunner;
import com.itdragon.StartApplication;
import com.itdragon.pojo.Address;
import com.itdragon.pojo.User;
import com.itdragon.repository.ITDragonMongoHelper;
/**
 * @RunWith    它是一個執行器
 * @RunWith(SpringRunner.class) 表示讓測試執行於Spring測試環境,不用啟動spring容器即可使用Spring環境
 * @SpringBootTest(classes=StartApplication.class)  表示將StartApplication.class納入到測試環境中,若不加這個則提示bean找不到。
 * @author itdragon
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes=StartApplication.class)
public class SpringbootStudyApplicationTests {
    @Autowired
    private ITDragonMongoHelper userMongoHelper; // 命名規則:需和MongodbBeansConfig配置Bean的方法名一致
    @Test
    public void createUser() {
        System.out.println("^^^^^^^^^^^^^^^^^^^^^^createUser");
        for (int i = 0; i < 25; i++) {    // 插入25條資料
            User user = new User();
            user.setId(Long.valueOf(userMongoHelper.getNextId(User.class.getName())));
            user.setAge(25 + i);
            user.setName("itdragon-" + i);
            Address address = new Address();
            address.setId(Long.valueOf(userMongoHelper.getNextId(Address.class.getName()))); 
            address.setProvince("湖北省");
            address.setCity("武漢市");
            user.setAddress(address);
            ArrayList<String> ability = new ArrayList<>();
            ability.add("Java");
            user.setAbility(ability);
            userMongoHelper.saveOrUpdate(user);
            System.out.println("user : " + user.toString());
        }
    }
    @Test
    public void updateUserName() {
        System.out.println("^^^^^^^^^^^^^^^^^^^^^^updateUserName");
        Map<String, Object> updateMap = new HashMap<>();
        // 查詢name為itdragon-1的資料,將name修改為ITDragonBlog
        User user = (User) userMongoHelper.findOne(Criteria.where("name").is("itdragon-1"));
        if (null == user) {
            System.out.println("^^^^^^^^^^^^^^^^^^^^^^User non-existent");
            return ;
        }
        updateMap.put("id", user.getId());
        updateMap.put("name", "ITDragonBlog");
        userMongoHelper.update(updateMap);
    }
    @Test
    public void updateUserAddress() {
        System.out.println("^^^^^^^^^^^^^^^^^^^^^^updateUserAddress");
        Map<String, Object> updateMap = new HashMap<>();
        User user = (User) userMongoHelper.findOne(Criteria.where("name").is("itdragon-3"));
        if (null == user) {
            System.out.println("^^^^^^^^^^^^^^^^^^^^^^User non-existent");
            return ;
        }
        Address address = new Address();
        address.setId(Long.valueOf(userMongoHelper.getNextId(Address.class.getName()))); 
        address.setProvince("湖南省");
        address.setCity("長沙");
        updateMap.put("id", user.getId());
        updateMap.put("address", address);
        userMongoHelper.update(updateMap);
    }
    @Test
    public void updateUserAbility() {
        System.out.println("^^^^^^^^^^^^^^^^^^^^^^updateUserAbility");
        Map<String, Object> updateMap = new HashMap<>();
        User user = (User) userMongoHelper.findOne(Criteria.where("name").is("itdragon-4"));
        if (null == user) {
            System.out.println("^^^^^^^^^^^^^^^^^^^^^^User non-existent");;
            return ;
        }
        ArrayList<String> abilitys = user.getAbility();
        abilitys.add("APP");
        updateMap.put("id", user.getId());
        updateMap.put("ability", abilitys);
        userMongoHelper.update(updateMap);
    }
    @Test
    public void findUserPage() {
        System.out.println("^^^^^^^^^^^^^^^^^^^^^^findUserPage");
        userMongoHelper.setOrderAscField("age"); // 排序
        Integer pageSize = 5; // 每頁頁數
        Integer pageNumber = 1; // 當前頁
        List<User> users = userMongoHelper.find(Criteria.where("age").gt(25), pageSize, pageNumber); // 查詢age大於25的資料
        for (User user : users) {
            System.out.println("user : " + user.toString());
        }
    }
}

MongoDB開發注意事項

MongoDB對錶結構要求不嚴,方便了我們的開發,同時也提高了犯錯率,特別是公司來了新同事,這顆地雷隨時都會爆炸。
第一點: MongoDB通過key獲取value的值。而這個value可以是內嵌的其他文件。因為沒有主外來鍵的概念,使用起來非常方便。若巢狀的文件太深,在更新資料是,需要注意不能覆蓋原來的值。比如User表中的ability是一個集合,若傳一個字串,依然可以更新成功,但已經破壞了資料結構。這是很多新手容易犯的錯。

第二點: 內嵌的文件屬性名最好不要重名。舉個例子,如果User表中的address物件,也有一個name的屬性。那麼在後續寫程式碼的過程中,極容易混淆。導致資料更新異常。

第三點: 表的設計儘量做到扁平化,單表設計能有效提高資料庫的查詢銷率。

第四點: 使用Mongoose約束資料結構,當資料結構不一致時操作失敗。

前兩點足以讓一些老輩程式設計師抓狂,讓新來的程式設計師懵圈。這也是很多開發人員喜歡又討厭MongoDB的原因。

總結

1 MongoDB是最接近關係型資料的非關係型資料庫中的文件型資料庫。

2 MongoDB支援非常豐富的查詢語句,功能強大,但容易犯錯。

3 MongoDB表結構的設計需謹慎,儘量減少巢狀層數,各巢狀的文件屬性名儘量避免相同。

參考文件

MongoDB官方文件: https://docs.mongodb.com

雙刃劍MongoDB的學習和避坑到這裡就結束了,感謝大家的閱讀,歡迎點評。如果你覺得不錯,可以”推薦“一下。也可以”關注“我,獲得更多豐富的知識。

相關文章