關(guān)于 mongoose 使用髓梅,這一篇就夠了

Node.js 中操作 MongoDB拟蜻,相信首選的庫(kù)肯定是 mongoose 了,但是實(shí)際項(xiàng)目中如果想要使用枯饿,還需要自己做封裝酝锅。但是考慮到 mongoose 長(zhǎng)長(zhǎng)的文檔,讓好多人望而怯步奢方。 筆者當(dāng)時(shí)打算用 mongoose 的時(shí)候搔扁,網(wǎng)上轉(zhuǎn)了一圈,發(fā)現(xiàn)多數(shù)就是抄抄官方文檔蟋字,于是硬著頭皮讀了一遍官方文檔稿蹲,依據(jù)項(xiàng)目需求做了簡(jiǎn)要封裝。
mongoose 非常的漂亮鹊奖,所以封裝的過(guò)程也非常的簡(jiǎn)潔苛聘。項(xiàng)目傳送門(mén)
筆者在擼碼的時(shí)候,是將結(jié)構(gòu)分成以下幾部分:

  • 連接層
  • Model 層
  • Dao 層
  • 應(yīng)用層

1. 連接層

代碼先放下邊了忠聚。代碼里注釋都已經(jīng)很明白了设哗,這里有幾點(diǎn)需要注意下:

  1. 要是用 Node.js 自帶的 Promise 替換 mongoose 中的 Promise,否則有時(shí)候會(huì)報(bào)警告
  2. 關(guān)于 MongoDB 的所有配置两蟀,我引用了 config 庫(kù)熬拒,配置都放在了 default.json 中
  3. 在使用 mongoose.createConnection(),并不是簡(jiǎn)單創(chuàng)建了一個(gè) mongo client垫竞,而是其內(nèi)部創(chuàng)建了一個(gè)連接池,默認(rèn)是維護(hù)了 5 個(gè) client蛀序。 而這個(gè)連接池我們可以根據(jù)自己的硬件和需求情況自定義欢瞪,只需要設(shè)置 options 中的 poolSize 參數(shù)即可。
  4. 如果你是單機(jī)的 mongoDB徐裸,那么只需要配置 default.json 文件中的 hostport即可遣鼓,replicaSet 中的 name 字段設(shè)置成空串就好
  5. 如果你要使用 mongoDB 集群配置,那就要用到 replicaSet重贺,此時(shí)填寫(xiě) default.json 中相應(yīng)的 replicaSet 配置即可骑祟。我在讀取配置文件再做相應(yīng)連接的時(shí)候邏輯都已經(jīng)處理了回懦。
    // mongo.js
    let mongoose = require('mongoose');
    let mongodbConfig = require('config').get('mongodb');
    
    /**
     * debug 模式
     */
    // mongoose.set('debug', true);
    
    /**
     * 使用 Node 自帶 Promise 代替 mongoose 的 Promise
     */
    mongoose.Promise = global.Promise;
    
    // 配置 plugin。此處配置 plugin 的話是全局配置次企,推薦在 每個(gè) Model 內(nèi) 自己定義
    // mongoose.plugin(require('./plugin').updatedAt);
    
    
    /**
     * 配置 MongoDb options
     */
    function getMongoOptions() {
        let options = {
            useMongoClient: true,
            poolSize: 5, // 連接池中維護(hù)的連接數(shù)
            reconnectTries: Number.MAX_VALUE,
            keepAlive: 120,
        };
    
        if (mongodbConfig.get('user')) options.user = mongodbConfig.get('user');
        if (mongodbConfig.get('pass')) options.pass = mongodbConfig.get('pass');
        if (mongodbConfig.get('replicaSet').get('name')) options.replicaSet = mongodbConfig.get('replicaSet').get('name');
        return options;
    }
    
    
    /**
     * 拼接 MongoDb Uri
     *
     * @returns {string}
     */
    function getMongoUri() {
        let mongoUri = 'mongodb://';
        let dbName = mongodbConfig.get('db');
        let replicaSet = mongodbConfig.get('replicaSet');
        if (replicaSet.get('name')) { // 如果配置了 replicaSet 的名字 則使用 replicaSet
            let members = replicaSet.get('members');
            for (let member of members) {
                mongoUri += `${member.host}:${member.port},`;
            }
            mongoUri = mongoUri.slice(0, -1); // 去掉末尾逗號(hào)
        } else {
            mongoUri += `${mongodbConfig.get('host')}:${mongodbConfig.get('port')}`;
        }
        mongoUri += `/${dbName}`;
    
        return mongoUri;
    }
    
    
    /**
     * 創(chuàng)建 Mongo 連接怯晕,內(nèi)部維護(hù)了一個(gè)連接池,全局共享
     */
    let mongoClient = mongoose.createConnection(getMongoUri(), getMongoOptions());
    
    /**
     * Mongo 連接成功回調(diào)
     */
    mongoClient.on('connected', function () {
        console.log('Mongoose connected to ' + getMongoUri());
    });
    /**
     * Mongo 連接失敗回調(diào)
     */
    mongoClient.on('error', function (err) {
        console.log('Mongoose connection error: ' + err);
    });
    /**
     * Mongo 關(guān)閉連接回調(diào)
     */
    mongoClient.on('disconnected', function () {
        console.log('Mongoose disconnected');
    });
    
    
    /**
     * 關(guān)閉 Mongo 連接
     */
    function close() {
        mongoClient.close();
    }
    
    
    module.exports = {
        mongoClient: mongoClient,
        close: close,
    };

2. Model

這一層我以創(chuàng)建 book collection 舉例缸棵。要?jiǎng)?chuàng)建一個(gè) Model舟茶,我將它分成以下幾部分

  1. schema

  2. plugin(后面單獨(dú)再說(shuō)這個(gè))

  3. hook

  4. 最后通過(guò) mongoClient.model() 創(chuàng)建出真正的 Model

     // book.js
     let {Schema} = require('mongoose');
     let {mongoClient} = require('../lib/mongo');
     
     
     /**
      * 操作 MongoDb 時(shí)需要?jiǎng)?chuàng)建兩個(gè)文件 model.js 和 modelDao.js
      *
      * 一. 對(duì)于 Model.js 以下幾部分:
      * 1. Schema 必要
      * 2. plugin 可選
      * 3. hook 可選
      * 4. 調(diào)用 mongoClient.model() 創(chuàng)建 Model,此處注意堵第,Model 名稱與 js 文件名一樣吧凉,但首字母大寫(xiě)
      *
      * 二. 對(duì)于 modelDao.js
      * 我們需要聲明一個(gè) ModelDao 的 class 繼承自 BaseDao, BaseDao 中包含基本的 crud 操作踏志,也可以根據(jù)需求自行定義
      *
      * 三. 外部使用
      * var dao = new ModelDao()
      * dao.crud();
      */
     
     
     const bookSchema = new Schema(
         {
             title: String,
             author: {type: String, lowercase: true}, // lowercase 都屬于 setters
         },
         {
             runSettersOnQuery: true // 查詢時(shí)是否執(zhí)行 setters
         }
     );
     
     
     /**
      * 配置 plugin
      */
     // (function () {
     //     let plugin = require('./plugin');
     //     bookSchema.plugin(plugin.createdAt);
     //     bookSchema.plugin(plugin.updatedAt);
     // })();
     
     
     /**
      * 配置 hook
      */
     // (function () {
     //     bookSchema.pre('update', function (next) {
     //         console.log('pre update');
     //         next();
     //     });
     //
     //     bookSchema.post('update', function (result, next) {
     //         console.log('post update', result);
     //         next();
     //     });
     //
     //     bookSchema.pre('save', function (next) {
     //         console.log('--------pre1------');
     //         next();
     //     });
     //
     //     bookSchema.pre('save', function (next) {
     //         console.log('--------pre2------');
     //         next(); // 如果有下一個(gè) pre(), 則執(zhí)行下一個(gè) pre(), 否則 執(zhí)行 save()
     //     });
     //     bookSchema.post('save', function (result, next) {
     //         console.log('---------post1----------', result);
     //         next();
     //     });
     //
     //     bookSchema.post('save', function (result, next) {
     //         console.log('---------post2----------', result);
     //         next(); // 如果有下一個(gè) post(), 則執(zhí)行下一個(gè) post(), 否則 結(jié)束
     //     });
     // })();
     
     
     /**
      * 參數(shù)一要求與 Model 名稱一致
      * 參數(shù)二為 Schema
      * 參數(shù)三為映射到 MongoDB 的 Collection 名
      */
     let Book = mongoClient.model(`Book`, bookSchema, 'book');
     
     module.exports = Book;
    

3. Dao 層

這部分代碼是 Dao 層的基類阀捅,封裝了基本的 crud 操作,在實(shí)現(xiàn)它的時(shí)候傳入對(duì)應(yīng)的 Model 即可针余∷潜桑可以參照項(xiàng)目中的 bookDao.js,如果不同的 dao 中需要定制不同的數(shù)據(jù)庫(kù)操作,那么只需要在實(shí)現(xiàn)類中增加相應(yīng)的方法即可涵紊。

    // baseDao.js
    class BaseDao {
        /**
         * 子類構(gòu)造傳入對(duì)應(yīng)的 Model 類
         *
         * @param Model
         */
        constructor(Model) {
            this.Model = Model;
        }
    
    
        /**
         * 使用 Model 的 靜態(tài)方法 create() 添加 doc
         *
         * @param obj 構(gòu)造實(shí)體的對(duì)象
         * @returns {Promise}
         */
        create(obj) {
            return new Promise((resolve, reject) => {
                let entity = new this.Model(obj);
                this.Model.create(entity, (error, result) => {
                    if (error) {
                        console.log('create error--> ', error);
                        reject(error);
                    } else {
                        console.log('create result--> ', result);
                        resolve(result)
                    }
                });
            });
        }
    
    
        /**
         * 使用 Model save() 添加 doc
         *
         * @param obj 構(gòu)造實(shí)體的對(duì)象
         * @returns {Promise}
         */
        save(obj) {
            return new Promise((resolve, reject) => {
                let entity = new this.Model(obj);
                entity.save((error, result) => {
                    if (error) {
                        console.log('save error--> ', error);
                        reject(error);
                    } else {
                        console.log('save result--> ', result);
                        resolve(result)
                    }
                });
            });
        }
    
    
        /**
         * 查詢所有符合條件 docs
         *
         * @param condition 查找條件
         * @param constraints
         * @returns {Promise}
         */
        findAll(condition, constraints) {
            return new Promise((resolve, reject) => {
                this.Model.find(condition, constraints ? constraints : null, (error, results) => {
                    if (error) {
                        console.log('findAll error--> ', error);
                        reject(error);
                    } else {
                        console.log('findAll results--> ', results);
                        resolve(results);
                    }
                });
            });
        }
    
    
        /**
         * 查找符合條件的第一條 doc
         *
         * @param condition
         * @param constraints
         * @returns {Promise}
         */
        findOne(condition, constraints) {
            return new Promise((resolve, reject) => {
                this.Model.findOne(condition, constraints ? constraints : null, (error, results) => {
                    if (error) {
                        console.log('findOne error--> ', error);
                        reject(error);
                    } else {
                        console.log('findOne results--> ', results);
                        resolve(results);
                    }
                });
            });
        }
    
    
        /**
         * 查找排序之后的第一條
         *
         * @param condition
         * @param orderColumn
         * @param orderType
         * @returns {Promise}
         */
        findOneByOrder(condition, orderColumn, orderType) {
            return new Promise((resolve, reject) => {
                this.Model.findOne(condition)
                    .sort({[orderColumn]: orderType})
                    .exec(function (err, record) {
                        console.log(record);
                        if (err) {
                            reject(err);
                        } else {
                            resolve(record);
                        }
                    });
            });
        }
    
    
        /**
         * 更新 docs
         *
         * @param condition 查找條件
         * @param updater 更新操作
         * @returns {Promise}
         */
        update(condition, updater) {
            return new Promise((resolve, reject) => {
                this.Model.update(condition, updater, (error, results) => {
                    if (error) {
                        console.log('update error--> ', error);
                        reject(error);
                    } else {
                        console.log('update results--> ', results);
                        resolve(results);
                    }
                });
            });
        }
    
    
        /**
         * 移除 doc
         *
         * @param condition 查找條件
         * @returns {Promise}
         */
        remove(condition) {
            return new Promise((resolve, reject) => {
                this.Model.remove(condition, (error, result) => {
                    if (error) {
                        console.log('remove error--> ', error);
                        reject(error);
                    } else {
                        console.log('remove result--> ', result);
                        resolve(result);
                    }
                });
            });
        }
    }


    module.exports = BaseDao;

4. 外部調(diào)用

new 對(duì)應(yīng)的 dao 實(shí)例傍妒,然后調(diào)用對(duì)應(yīng)的數(shù)據(jù)庫(kù)操作就好了

    let bookDao = new BookDao();
    bookDao.crud();

5. plugin

在不同的 collection 中可能會(huì)有一些通用的操作,這時(shí)候摸柄,我們就可以把這種操作封裝進(jìn) plugin 中颤练。 比如 doc 的 插入時(shí)間和更改時(shí)間。下面是一個(gè)例子:

    /**
     * doc 創(chuàng)建時(shí)間 plugin驱负。其中在 插入一條 doc 時(shí) 同時(shí)創(chuàng)建 createdAt 和 updateAt 字段
     *
     * @param schema
     * @param options
     */
    function createdAt(schema, options) {
        schema.add({createdAt: Date});
        schema.add({updatedAt: Date});
    
        schema.pre('save', function (next) {
            let now = Date.now();
            this.createdAt = now;
            this.updatedAt = now;
            next();
        });
    
        if (options && options.index) {
            schema.path('createdAt').index(options.index);
            schema.path('updatedAt').index(options.index);
        }
    }
    
    
    /**
     * doc 更新時(shí)間 plugin
     *
     * @param schema
     * @param options
     */
    function updatedAt(schema, options) {
        schema.pre('update', function (next) {
            this.update({}, {$set: {updatedAt: new Date()}});
            next();
        });
    
        if (options && options.index) {
            schema.path('updatedAt').index(options.index);
        }
    }
    
    
    module.exports = {
        createdAt: createdAt,
        updatedAt: updatedAt,
    };

最后再次獻(xiàn)上項(xiàng)目傳送門(mén) 嗦玖,感謝您的閱讀,也歡迎您拍磚跃脊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宇挫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子酪术,更是在濱河造成了極大的恐慌器瘪,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绘雁,死亡現(xiàn)場(chǎng)離奇詭異橡疼,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)庐舟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)欣除,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人挪略,你說(shuō)我怎么就攤上這事历帚√显溃” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵挽牢,是天一觀的道長(zhǎng)谱煤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)卓研,這世上最難降的妖魔是什么趴俘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮奏赘,結(jié)果婚禮上寥闪,老公的妹妹穿的比我還像新娘。我一直安慰自己磨淌,他們只是感情好疲憋,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著梁只,像睡著了一般缚柳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搪锣,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天秋忙,我揣著相機(jī)與錄音,去河邊找鬼构舟。 笑死灰追,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的狗超。 我是一名探鬼主播弹澎,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼努咐!你這毒婦竟也來(lái)了苦蒿?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤渗稍,失蹤者是張志新(化名)和其女友劉穎佩迟,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體竿屹,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡音五,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了羔沙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡厨钻,死狀恐怖扼雏,靈堂內(nèi)的尸體忽然破棺而出坚嗜,到底是詐尸還是另有隱情,我是刑警寧澤诗充,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布苍蔬,位于F島的核電站,受9級(jí)特大地震影響蝴蜓,放射性物質(zhì)發(fā)生泄漏碟绑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一茎匠、第九天 我趴在偏房一處隱蔽的房頂上張望格仲。 院中可真熱鬧,春花似錦诵冒、人聲如沸凯肋。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)侮东。三九已至,卻和暖如春豹芯,著一層夾襖步出監(jiān)牢的瞬間悄雅,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工铁蹈, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宽闲,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓木缝,卻偏偏與公主長(zhǎng)得像便锨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子我碟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 本教程介紹如何在 Azure 中的 Linux VM 上實(shí)現(xiàn) MongoDB、Express厘托、AngularJS ...
    ITknight閱讀 357評(píng)論 0 1
  • 單例模式 適用場(chǎng)景:可能會(huì)在場(chǎng)景中使用到對(duì)象友雳,但只有一個(gè)實(shí)例,加載時(shí)并不主動(dòng)創(chuàng)建铅匹,需要時(shí)才創(chuàng)建 最常見(jiàn)的單例模式押赊,...
    Obeing閱讀 2,056評(píng)論 1 10
  • 有的放矢的基本原則: 1、到用戶中去:你要做的第一件事就是從該死的辦公大樓里走出去包斑,不到用戶中去流礁,你就無(wú)法檢驗(yàn)自己...
    阿東咚咚咚閱讀 3,224評(píng)論 0 1
  • 白舟迎道涕俗,暮下輕波渺。 五影入幽居神帅,飲佳醪再姑,樽前談笑。 月斜星沃找御,纖指向天官元镀,歌淺調(diào)。 天欲曉霎桅,煙鎖鹽城角栖疑。 閨中...
    一草雨田閱讀 179評(píng)論 0 2
  • 撰文&攝影:跫音琳瑯 走, 在凜冽中穿越 對(duì)岸 不確定的未來(lái)哆档!
    太平楊雁閱讀 66評(píng)論 0 0