1. 程式人生 > >Node.js 模組之【mongoose】MongoDB操作模組

Node.js 模組之【mongoose】MongoDB操作模組

1. 安裝mongoose

npm i mongoose --save-dev

2. 引入mongoose

const mongoose = require('mongoose');

3. 連線資料庫

mongoose.Promise = global.Promise;  
mongoose.connect(DBConfig.DBUrl);

4. mongoose支援的資料型別

- String
- Number
- Date
- Buffer
- Boolean
- Mixed
- ObjectId
- Array

5. 定義Schema方式

  • 構造器方式
var userSchema = new Schema({
    username: 'string'
});
  • 追加方式
var userSchema = new Schema;
userSchema.add({username: 'string'});

6. 定義Schema的例項方法

// 定一個使用者Schema
var userSchema  = new Schema({username: 'string'});

// 為使用者Schema新增一個detial例項方法
userSchema.methods.detial = function
(callback){
return this.model('user').find({}, callback); } // 將使用者Schema編譯為使用者模型 var UserModel = mongoose.model('user', userSchema); // 通過模型構造器,建立一個使用者物件 var user = new UserModel({username:'zhagener', password:'123'}); // 呼叫使用者物件的detail例項方法 user.detial(function(err,docs){ console.log(docs); });

7. 定義Schema的靜態方法

// 定一個使用者Schema
var userSchema  = new Schema({username: 'string'});

// 為使用者Schema新增一個findByName靜態方法
userSchema.statics.findByName = function(username, callback){
    return this.find({ username: username }, callback);
} 

// 將使用者Schema編譯為使用者模型
var UserModel =  mongoose.model('user', userSchema);

// // 呼叫使用者模型的findByName靜態方法
UserModel.findByName('zhagener', function(err, users) {
  console.log(users);
});

8. 為Schema定義查詢助手

// 定義使用者Schema
var userSchema = new Schema({username: 'string'});

// 定義使用者Schema的查詢助手
userSchema.query.byName = function(username){
    return this.find({username: username});
};

// 使用查詢助手
var UserModel = mongoose.model('user', userSchema);

UserModel.find().byName('zhagener').exec(function(err,users){
    console.log(docs);
});

9. 為Schema定義虛擬函式

Virtuals are document properties that you can get and set but that do not get persisted to MongoDB. The >getters are useful for formatting or combining fields, while setters are useful for de-composing a single value >into multiple values for storage.

文件屬性中的虛擬函式能夠設定或獲取,但是不會持久化到MongoDB中,其中getter對於格式化或者合併欄位是非常適用,setter用於將單個值解析多個值進行儲存非常適用。

// 建立使用者Schema
var userSchema = new Schema({
    name:{
        firstName: 'string',
        lastName: 'string'
    }
});

// 為使用者Schema新增全名虛擬函式(getter)
userSchema.virtual('fullName').get(function(){
    return this.name.firstName + ' ' + this.name.lastName;
});

// 為使用者Schema新增全名虛擬函式(setter)
userSchema.virtual('fullName').set(function(fullName){
    var names = fullName.split(/\s+/);
    this.name.firstName = names[0];
    this.name.lastName = names[1];
});


// 將使用者Schema編譯為使用者模型
var UserModel = mongoose.model('user', userSchema);

// 建立一個使用者例項
var user = new UserModel({name:{firstName:'zhagener', lastName:'qier'}});

// 呼叫使用者例項的虛擬函式fullName(getter)
console.log(user.fullName);

// 呼叫使用者例項的虛擬函式fullName(setter)
user.fullName = 'a b';

// 呼叫使用者例項的虛擬函式fullName(getter)
console.log(user.fullName);

10. Schema選項

10.1 Schema選項設定方法

  • 構造器方式(集中配置)
new Schema({..}, options);// options : json object
  • 追加方式(單個追加)
var schema = new Schema({..});
schema.set(option, value);

10.2 Schema選項列表

- autoIndex
- capped
- collection
- emitIndexErrors
- id
- _id
- minimize
- read
- safe
- shardKey
- strict
- toJSON
- toObject
- typeKey
- validateBeforeSave
- versionKey
- skipVersioning
- timestamps
- retainKeyOrder
10.2.1 Schema選項:autoIndex

建立不建立索引的Schema

// 禁止自動建立索引
var userSchema = new Schema({..}, { autoIndex: false });
var UserModel = mongoose.model('user',userSchema);
UserModel.ensureIndexes(callback);
10.2.2 Schema選項:timestamp

建立預設存在{updatedAt 、created_at}欄位的Schema

var thingSchema = new Schema({..}, { timestamps: { createdAt: 'created_at' } });
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing();
thing.save(); // `created_at` & `updatedAt` will be included

11. Schema 型別:string (詳細)

11.1 string 列舉值限定

自定義錯誤訊息

// 定義性別列舉型別
var sexs = {
  values: ['male', 'female'],
  message: 'sexs not exist : `{PATH}` the value `{VALUE}`'
}

// 建立使用者Schema
var userSchema = new Schema({ sex: { type: String, enum: sexs }});

// 將使用者Schema編譯為使用者模型
var UserModel = mongoose.model('user', userSchema);

// 建立使用者例項(zhagener),並賦值一個非法的性別型別 `帥哥`
var zhagener = new UserModel({ sex: 'Handsome guy' });

// 儲存使用者例項(zhagener)
zhagener.save(function (err) {
  console.error(String(err)) 
  zhagener.sex = 'male' // 報錯後重置性別為合法的男性
  zhagener.save(function(err) {
    console.log(err);
  }) // success
});

11.2 string value大/小寫化

儲存時,將所有字元轉換為大/小寫

var userSchema = new Schema({email:{type: String, lowercase: true}}); // 大寫:uppercase
var UserModel = mongoose.model('user',userSchema);
var zhagener = new UserModel({email:'[email protected]'});
zhagener.save();// email:[email protected]

11.3 string 正則約定化

儲存時,對value進行正則驗證

var matchRex = [/^China/,"your contry is not china : {VALUE}"];
var userSchema = new Schema({contry:{type:String, match:matchRex}});
var UserModel = mongoose.model('user',userSchema);
var zhagener = new UserModel({contry:'Bangzi'});
zhagener.validate(function(err) {
    console.log(err);// ValidatorError: your contry is not china : bangzi
});

11.3 string 最大/小長度限定

儲存時,對value進行最大長度限定

var maxlength = [11, "your phone number length is invalid (11) : {VALUE}"];
var userSchema = new Schema({phoneNo:{type:String, maxlength:maxlength}}); // 最小長度:minlength 
var UserModel = mongoose.model('user',userSchema);
var zhagener = new UserModel({phoneNo:135147599999});
zhagener.save(function(err) {
    console.log(err);// ValidatorError: your phone number length is invalid (11) : 135147599999
    zhagener.phoneNo = 13514759999;
    zhagener.save();
});

11.4 string 修剪

儲存時,對value進行去前後空格

var userSchema = new Schema({username:{type: String, trim: true}});
var UserModel = mongoose.model('user',userSchema);
var zhagener = new UserModel({username: ' hhahaha      '}); //    "username" : "hhahaha",
zhagener.save(function(err) {
    if (err) {
        console.log(err)
    }
});

12. Schema 型別:Number(詳細)

12.1 number 最大/小值限定

自定義錯誤訊息

var max = [150, "you are Tortoise ? (age < 150): {VALUE}"]
var userSchema = new Schema({age:{type: Number, max:max}});// 最小值:min
var UserModel = mongoose.model('user',userSchema);
var zhagener = new UserModel({age: 200}); 
zhagener.validate(function(err) {
    console.log(err);// ValidatorError: you are Tortoise ? (age < 150): 200
});

13. Schema 型別:Date(詳細)

13.1 Date 最大/小值限定

自定義錯誤訊息

var max = [Date('2014-10-01'), "max time Date('2014-10-01'): {VALUE}"] //最小值:min
var userSchema = new Schema({
    createdAt: {
        type: Date,
        max: max
    }
});
var UserModel = mongoose.model('user', userSchema);
var zhagener = new UserModel({
    createdAt:Date.now()
}); 
zhagener.validate(function(err) {
    console.log(err); // ValidatorError: max time Date('2014-10-01'): Thu Mar 16 2017 19:09:26 GMT+0800 (中國標準時間)
});

14. Model(模型)(詳細)

14.1 通過模型建立例項方式

  • 構造器方式(需要呼叫例項的save儲存至資料庫)
var userSchema = new Schema({
    username: 'string',
    password: 'string'
});
UserModel =  mongoose.model('user', userSchema);
var zhagener = new UserModel({username: 'zhagener'});
  • 呼叫create方法方式(直接儲存至資料庫)
UserModel.create({username: 'zhagener'},function(err,zhagener) {
    if (err) {}
    // zhagener has save to mongoDB
});

14.2 通過模型查詢資料

UserModel.find().where('username').eq('zhagener').exec(function(err,docs) {
    console.log(docs);// [ { _id:58ca7a0f922f59379cae3778, username:'zhagener', __v: 0 } ]
});

14.3 通過模型刪除資料

UserModel.create({username: 'zhagener'},function(err,zhagener) {
    if (err) {}
    // zhagener has save to mongoDB
    UserModel.remove({username:'zhagener'},function(err) {
        if(err)
            console.log(err);
    })
    UserModel.find().where('username').eq('zhagener').exec(function(err,docs) {
        console.log(docs);// [{_id:58ca7a0f922f59379cae3778, username:'zhagener', __v:0 } ]
    });
});

15. Document(文件)(詳細)

15.1 更新資料的兩種方式

UserModel.create({username: 'zhagener'},function(err,zhagener) {
    UserModel.find().where('username').eq('zhagener').exec(function(err,docs) {
        console.log(docs); 
        UserModel.findByIdAndUpdate(docs[0]._id, 
            { 
                $set: { username: 'zhagener2' }
            }, 
            { new: true }, 
            function (err, zhagener2) {
              if (err) return handleError(err);
                  console.log(zhagener2);
        });
    });

});
UserModel.create({username: 'zhagener'},function(err,zhagener) {
    if (err) {}
    // zhagener has save to mongoDB
    // UserModel.remove({username:'zhagener'},function(err) {
    //  if(err)
    //      console.log(err);
    // })
    UserModel.find().where('username').eq('zhagener').exec(function(err,docs) {
        console.log(docs);// [ { _id: 58ca7a0f922f59379cae3778, username: 'zhagener', __v: 0 } ]
        UserModel.update({username:'zhagener'},{$set:{username:'haha'}},function(err,docs) {
            if (err) {}
            console.log(docs)// haha
        })
    });

});

16. 子文件

16.1 定義子文件

Sub-documents enjoy all the same features as normal documents. The only difference is that they are not saved individually, they are saved whenever their top-level parent document is saved.

子文件擁有一個正常文件的所有特性,唯一的不同是不能夠獨立的儲存,只有在父級文件儲存時子文件同時也被儲存。

var UserSchema = new Schema({
    username: {
        type: String
    }, 
    password: {
        type: String
    },
    githubId: {
        type: String
    },
    blogs:[BlogSchema] // 指定字表Schema
});
// 將使用者Schema編譯為使用者模型
var UserModel = mongoose.model('User', UserSchema);
// 建立使用者例項zhagener
var zhagener = new UserModel({
    username: 'zhagener',
    blogs: [{
        title: 'first',
        content: 'first content'
    }, {
        title: 'second',
        content: 'second content'
    }]
});
zhagener.save();

效果

16.2 子文件錯誤冒泡

If an error occurs in a sub-document’s middleware, it is bubbled up to the save() callback of the parent, so error handling is a snap!

如果子文件操作的中介軟體中發生異常,異常將會冒泡至父級儲存的回撥中。


var BlogSchema = new Schema({
    title:{type: String},
    content:{type: String}
});

BlogSchema.pre('save',function(next) {
    if(this.title == 'first'){
        return next(new Error('title can not defined as first'));
    }
});


var UserSchema = new Schema({
    username: {
        type: String
    }, //使用者賬號
    password: {
        type: String
    },
    githubId: {
        type: String
    },
    blogs:[BlogSchema]
});

var UserModel = mongoose.model('User', UserSchema);

var zhagener = new UserModel({
    username: 'zhagener',
    blogs: [{
        title: 'first',
        content: 'first content'
    }, {
        title: 'second',
        content: 'second content'
    }]
});

zhagener.save(function(err,docs) {
    if(err){
        console.log(err);// title can not defined as first
    }
});

16.3. 查詢一個子文件

Each document has an _id. DocumentArrays have a special id method for looking up a document by its _id.

每個文件都有一個_id欄位,文件陣列有一個id方法,通過此方法傳入子文件的_id欄位就可查到子文件

async.waterfall([
    // 儲存使用者例項,並新增上兩個部落格
    function(callback) {
        var zhagener = new UserModel({
            username: 'zhagener',
            blogs: [{
                title: 'first blog',
                content: 'first content'
            }, {
                title: 'first blog',
                content: 'first content'
            }]
        });
        zhagener.save();
        callback(null,zhagener)
    },
    // 獲取例項物件
    function(newUser,callback) {
        UserModel.find({username:newUser.username}).exec(function(err, docs) {
            var zhagener = docs[0];
            callback(null,zhagener);
        })
    },
    // 根據例項物件查詢對應的第一篇部落格內容
    function(owner,callback) {
        try{
            var blog = owner.blogs.id(owner.blogs[0]._id);
            console.log("+++blog:"+blog);
            callback(null, blog);

        }catch(error){
            callback(error);
        }
    }

    ],function(err,results) {
    console.log("Results: "+results);
});

16.4. 操作子文件(push)

MongooseArray methods such as push, unshift, addToSet, and others cast arguments to their proper types transparently:

mongoose中的陣列的方法(push、unshift、addToSet….)及引數,都類同原生JS

var anonymous = new UserModel;
anonymous.blogs.push({
    title: 'first blog',
    content: 'first content'
}, {
    title: 'second blog',
    content: 'second content'
});
var firstBlog = anonymous.blogs[0];
console.log("isNew:___ "+firstBlog.isNew); // isNew:___ true
anonymous.save(function(err) {
    if (err) {
        console.log(err);
    }
    console.log(anonymous);
})

16.5. 操作子文件(create)

Sub-docs may also be created without adding them to the array by using the create method of MongooseArrays.

子文件也可通過Mongoose陣列的create方法進行建立,但是建立的文件不會新增到父級文件中

var newdoc = anonymous.blogs.create({ title: 'first', content: 'first content' });
console.log(anonymous); // blogs:[]

16.5. 操作子文件(remove)

async.waterfall([
    // 查詢zhagener物件(用於兩篇部落格)
    function(callback) {
        UserModel.findOne({username:'zhagener'},function(err,doc) {
            if (err) {
                callback(err);
            }
            callback(null,doc);
        })
    },
    // 刪除zhagener物件的第一篇部落格
    function(zhagener,callback) {
        zhagener.blogs.id(zhagener.blogs[0]._id).remove();
        callback(null,zhagener);
    },
    // 儲存zhagener物件
    function(zhagener,callback) {
        zhagener.save();
        callback(null,zhagener);
    }

    ],function(err,results) {
    if (err) {
        console.log(err);
    }
    console.log(results);
});

16.6 另外一種宣告字表語法

If you don’t need access to the sub-document schema instance, you may also declare sub-docs by simply passing an object literal:

如果你不需要訪問子文件的例項是,你還可以通過一個物件宣告字表
注:可能對於字表的操作性變得不那麼靈活,比如虛擬函式就不能夠使用,次場景只使用[特別….特別]簡單的子文件時使用。

var userSchema = new Schema({
  blogs: [{ title: 'string' , content: 'string'}]
})

16.7 父級文件嵌入單個子文件

var githubSchema = new Schema({ name: 'string', githubId: 'string' });

var UserSchema = new Schema({
  githubCount: githubSchema
});

17. mongoose中的文件查詢

mongoose中的查詢提供了兩種方式:

  • JSON doc方式
Person.
  find({
    occupation: /host/,
    'name.last': 'Ghost',
    age: { $gt: 17, $lt: 66 },
    likes: { $in: ['vaporizing', 'talking'] }
  }).
  limit(10).
  sort({ occupation: -1 }).
  select({ name: 1, occupation: 1 }).
  exec(callback);
  • Query builder方式
Person.
  find({ occupation: /host/ }).
  where('name.last').equals('Ghost').
  where('age').gt(17).lt(66).
  where('likes').in(['vaporizing', 'talking']).
  limit(10).
  sort('-occupation').
  select('name occupation').
  exec(callback);

18. 流資料查詢

待完成

19. 多表關聯

There are no joins in MongoDB but sometimes we still want references to documents in other collections. This is where population comes in.

在MongoDB中沒有多表關聯的,但是我們可以通過引用其他文件,這就是population的概念

Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s). We may populate a single document, multiple documents, plain object, multiple plain objects, or all objects returned from a query. Let’s look at some examples.

Population 能夠將制定的引用文件自動的替換到主文件中,你可以制定單個文件、多個文件、單個普通物件,多個普通物件,再或者是查詢出來的文件

19.1 Schema中建立引用關係

下面我們以【作者-書】為例:
- 作者可能會有多本書
- 每本書僅會歸屬一個作者(排除:合著情況)

// 定義作者Schema
var AuthorSchema = new Schema({
    _id: {type: 'number'},
    name: {type: 'string'},
    books: [{type: Schema.Types.ObjectId, ref:'book'}]
});

// 定義書Schema
var BookSchema = new Schema({
    _author: {type:'number', ref:'author'},
    name: {type: 'string'}
});

var AuthorModel = mongoose.model('author', AuthorSchema);
var BookModel = mongoose.model('book', BookSchema);

19.2 儲存引用文件的資料

// 建立作者zhagener
var zhagener = new AuthorModel({_id:0,name:'zhagener'});

// 儲存作者zhagener
zhagener.save(function(err) {
    if (err) {
        console.log(err)
    }
    console.log('save Author successfully')

    // 根據作者的_id,繼續新增他的第一本書例項
    var mongooseInAction = new BookModel({
        _author:zhagener._id,
        name: 'mongoose in action'
    });

    // 儲存第一本書例項
    mongooseInAction.save(function(err) {
        if (err) {
            console.log(err);
        }
        console.log('save Book successfully')
    })
})

19.2 關聯引用文件查詢

  • 查詢書的所有資訊包括作者(所有資訊)
BookModel
    .findOne({name:'mongoose in action'})
    .populate('_author')
    .exec(function(err,book) {
        if (err) {
            console.log(err);
        }
        console.log(book);
        // { _id: 58cb73aaf5e795303808230e,
        //   _author: { _id: 0, name: 'zhagener', __v: 0, books: [] },
        //   name: 'mongoose in action',
        //   __v: 0 }
    });
  • 查詢書的所有資訊包括作者(指定查詢域)
BookModel
    .findOne({name:'mongoose in action'})
    .populate('_author','name')
    .exec(function(err,book) {
        if (err) {
            console.log(err);
        }
        console.log(book);
        // { _id: 58cb73aaf5e795303808230e,
        //   _author: { _id: 0, name: 'zhagener' },
        //   name: 'mongoose in action',
        //   __v: 0 }
    });

19.2 關聯引用文件更新

BookModel
    .findOne({name:'mongoose in action'})
    .exec(function(err,book) {
        if (err) {
            console.log(err);
        }
        var zhagener2 = new AuthorModel({_id:1,name:'zhagener2'});
        book._author = zhagener2;
        console.log(book);
        // { _id: 58cb73aaf5e795303808230e,
        //   _author: { _id: 1, name: 'zhagener2', books: [] },
        //   name: 'mongoose in action',
        //   __v: 0 }
    });

19.3 多個引用文件(儲存)

下面我們以【書-作者 書-章節】為例:
- 書對應一個作者
- 作者會有多本書
- 每本書會有很多章節

// 定義作者Schema
var AuthorSchema = new Schema({
    _id: {type: 'number'},
    name: {type: 'string'},
    books: [{type: Schema.Types.ObjectId, ref:'book'}]
});

// 定義書Schema
var BookSchema = new Schema({
    _id: {type: 'number'},
    _author: {type: 'number', ref:'author'},
    name: {type: 'string'},
    chapters: [{type: Schema.Types.ObjectId, ref:'chapter'}]
});

// 定義章節Schema
var ChapterSchema = new Schema({
    _id: {type: 'number'},
    _book: {type: 'number', ref:'chapter'},
    title: {type: 'string'},
    content: {type: 'string'}
});

var AuthorModel = mongoose.model('author', AuthorSchema);
var BookModel = mongoose.model('book', BookSchema);
var ChapterModel = mongoose.model('chapter', ChapterSchema);

var zhagener = new AuthorModel({_id:0,name:'zhagener'});

// 儲存作者zhagener
zhagener.save(function(err) {
    if (err) {
        console.log(err)
    }
    console.log('save Author successfully')

    // 根據作者的_id,繼續新增他的第一本書例項
    var mongooseInAction = new BookModel({
        _id:0,
        _author:zhagener._id,
        name: 'mongoose in action'
    });

    // 儲存第一本書例項
    mongooseInAction.save(function(err) {
        if (err) {
            console.log(err);
        }
        var mongooseIntro = new ChapterModel({
                _id: 0,
                _book:mongooseInAction._id,
                content:'mongoose introduction content'
            });
            mongooseIntro.save(function(err) {
                if (err) {
                    console.log(err);
                }
                console.log('save Chapter successfully');
        });
        console.log('save Book successfully')
    });
});

19.4 多個引用文件(查詢)