連接mongoose
mongoose連接數據庫有兩種方式
第一種:
'use strict'; const mongoose = require('mongoose'); mongoose.connect('mongodb://localhost:27017/test'); const con = mongoose.connection; con.on('error', console.error.bind(console, '連接數據庫失敗')); con.once('open',()=>{ //成功連接 })
第二種:
var mongoose = require('mongoose'); db = mongoose.createConnection('localhost', 'test'); var schema = new mongoose.Schema({ name: String }); var collectionName = 'kittens'; var M = db.model('Kitten', schema, collectionName); var silence = new M({ name: "Silence"}); silence.save(function(err){ });
mongoose.createConnection()和mongoose.connect()區別
首先,我們需要定義一個連接,如果你的app只用到一個數據庫,你應該使用 mongoose.connect。如果你還需要連接其他數據庫,使用mongoose.createConnection。
所有的 connect and createConnection 使用 mongodb:// URI, or the parameters host, database, port, options.等參數
var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/my_database');
一旦連接,Connection對象會觸發open事件,如果你使用 mongoose.connect, Connection對象就是默認的 mongoose.connection,而使用 mongoose.createConnection 返回值就是 Connection.
Mongoose會緩存所有命令直到連接上數據庫,這意味着你不必等待它連接MongoDB再定義 models,執行 queries 等。
Mongoose基本概念
- Schema: 表定義模板
- Model: 類似關系數據庫表,封裝成具有一些集合操作的對象
- instance: 類似記錄,由Model創建的實體,也具有影響數據庫的操作
基本用法
//定義一個schema let Schema = mongoose.Schema({ category:String, name:String }); Schema.methods.eat = function(){ console.log("I've eatten one "+this.name); } //繼承一個schema let Model = mongoose.model("fruit",Schema); //生成一個document let apple = new Model({ category:'apple', name:'apple' }); //存放數據 apple.save((err,apple)=>{ if(err) return console.log(err); apple.eat(); //查找數據 Model.find({name:'apple'},(err,data)=>{ console.log(data); }) });
Schema
// from mongoose author var mongoose = require('mongoose'); var Schema = mongoose.Schema;//引用出來,不需要每次調用 mongoose.Schema()這個丑陋的API. var blogSchema = new Schema({ title: String, author: String, //直接寫法,會被轉化成相應的SchemaType body: String, comments: [{ body: String, date: Date }], //定義SchemaType寫法 date: { type: Date, default: Date.now }, hidden: Boolean, meta: { votes: Number, favs: Number } });
Schema 之所以能夠定義documents, 是因為他可以限制你輸入的字段及其類型. mongoose支持的基本類型有:
- String
- Number
- Date
- Buffer
- Boolean
- Mixed
- ObjectId
- Array
SchemaType
type屬性指定SchemaType類型,不同的SchemaType類型還有其他不同的屬性配置
var schema2 = new Schema({ test: { type: String, lowercase: true // Always convert `test` to lowercase } });
這是所有類型公有的:
- required: 必選驗證器。
- default: 默認值。Any或function,如果該值是一個函數,則該函數的返回值將用作默認值。
- select: boolean值, 指定是否被投影
- `validate: 驗證器
- get: get方法,using Object.defineProperty().
- set: set方法 using Object.defineProperty().
- alias: 別名。
設置索引
這里設置索引分兩種,一種設在Schema filed, 另外一種設在 Schema.index 里.
//在field 設置 var animalSchema = new Schema({ name: String, type: String, tags: { type: [String], index: true } });
//在Schema.index中設置.
animalSchema.index({ name: 1, type: -1 }); //1 表示正序, -1 表示逆序
實際上,兩者效果是一樣的. 看每個人的喜好了. 不過推薦直接在Schema level中設置, 這樣分開能夠增加可讀性. 不過,
可以說,當應用啟動的時候, ,Mongoose會自動為Schema中每個定義了索引的調用ensureIndex,確保生成索引.
並在所有的secureIndex調用成功或出現錯誤時,在 Model 上發出一個'index'事件。 開發環境用這個很好, 但是建議在生產環境不要使用這個.使用下面的方法禁用ensureIndex。
通過將 Schema 的autoIndex選項設置為false或通過將選項config.autoIndex設置為false將連接全局設置為禁用此行為 有可能嚴重拖慢查詢或者創建速度,所以一般而言,我們需要將該option 關閉.
mongoose.connect('mongodb://user:pass@localhost:port/database', { config: { autoIndex: false } }); //真心推薦 // or mongoose.createConnection('mongodb://user:pass@localhost:port/database', { config: { autoIndex: false } }); //不推薦 // or animalSchema.set('autoIndex', false); //推薦 // or new Schema({..}, { autoIndex: false }); //懶癌不推薦
// Will cause an error because mongodb has an _id index by default that // is not sparse animalSchema.index({ _id: 1 }, { sparse: true }); var Animal = mongoose.model('Animal', animalSchema); Animal.on('index', function(error) { // "_id index cannot be sparse" console.log(error.message); });
定義Schema.methods
// 定義一個schema var freshSchema = new Schema({ name: String, type: String }); // 添加一個fn. animalSchema.methods.findSimilarTypes = function (cb) { //這里的this指的是具體document上的this //this.model 返回Model對象 return this.model ('Animal').find({ type: this.type }, cb); } // 實際上,我們可以通過schema綁定上,數據庫操作的所有方法. // 該method實際上是綁定在 實例的 doc上的
實例Model
這里同樣很簡單,只需要 mongoose.model() 即可.
//生成,model 類. 實際上就相當於我們的一個collection var Animal = mongoose.model('Animal', animalSchema); var dog = new Animal({ type: 'dog' });
但是, 這里有個問題. 我們在Schema.methods.fn 上定義的方法,只能在 new Model() 得到的實例中才能訪問. 那如果我們想,直接在Model上調用 相關的查詢或者刪除呢?
綁定Model方法
同樣很簡單,使用 Statics 即可.
// 給model添加一個findByName方法 animalSchema.statics.findByName = function (name, cb) { //這里的this 指的就是Model return this.find({ name: new RegExp(name, 'i') }, cb); } var Animal = mongoose.model('Animal', animalSchema); Animal.findByName('fido', function (err, animals) { console.log(animals); });
虛擬屬性
Mongoose 還有一個super featrue-- virtual property 該屬性是直接設置在Schema上的. 但是,需要注意的是,VR 並不會真正的存放在db中. 他只是一個提取數據的方法.
//schema基本內容 var personSchema = new Schema({ name: { first: String, last: String } }); // 生成Model var Person = mongoose.model('Person', personSchema); //現在我們有個需求,即,需要將first和last結合輸出. //一種方法是,使用methods來實現 //schema 添加方法 personSchema.methods.getName = function(){ return this.first+" "+this.last; } // 生成一個doc var bad = new Person({ name: { first: 'jimmy', last: 'Gay' } }); //調用 bad.getName();
但是,像這樣,僅僅這是為了獲取一個屬性, 實際上完全可以使用虛擬屬性來實現.
//schema 添加虛擬屬性 personSchema.virtual('fullName').get(function(){ return this.first+" "+this.last; }) //調用 bad.fullName; //和上面的方法的結果是完全一致的
而且,經過測試, 使用fn實現的返回,比VR 要慢幾十倍. 一下是測試結果:
console.time(1); bad.getName(); console.timeEnd(1); console.time(2); bad.fullName; console.timeEnd(2); //結果為: 1: 4.323ms; //method 2: 0.253ms // VR
最后再補充一下,Schema中初始化的相關參數.
Schema參數 在 new Schema([options]) 中,我們需要設置一些相關的參數.
- safe: 用來設置安全模式. 實際上,就是定義入庫時數據的寫入限制. 比如寫入時限等.
//使用安全模式. 表示在寫入操作時,如果發生錯誤,也需要返回信息. var safe = true; new Schema({ .. }, { safe: safe }); // 自定義安全模式. w為寫入的大小范圍. wtimeout設置寫入時限. 如果超出10s則返回error var safe = { w: "majority", wtimeout: 10000 }; new Schema({ .. }, { safe: safe });
- toObject: 用來表示在提取數據的時候, 把documents 內容轉化為Object內容輸出. 一般而言只需要設置getters為true即可.
var schema = new Schema({ name: String }); schema.path('name').get(function (v) { return v + ' is my name'; }); schema.set('toObject', { getters: true }); var M = mongoose.model('Person', schema); var m = new M({ name: 'Max Headroom' }); console.log(m); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }
- toJSON: 該是和toObject一樣的使用. 通常用來把 documents 轉化為Object. 但是, 需要顯示使用toJSON()方法,
var schema = new Schema({ name: String }); schema.path('name').get(function (v) { return v + ' is my name'; }); schema.set('toJSON', { getters: true, virtuals: false }); var M = mongoose.model('Person', schema); var m = new M({ name: 'Max Headroom' }); console.log(m.toObject()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom' } console.log(m.toJSON()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' } // since we know toJSON is called whenever a js object is stringified: console.log(JSON.stringify(m)); // { "_id": "504e0cd7dd992d9be2f20b6f", "name": "Max Headroom is my name" }
Model
model的創建
model的創建實際上就是方法的copy. 將schema上的方法,copy到model上. 只是copy的位置不一樣, 一部分在prototype上, 一部分在constructor中.
//from mongoosejs var schema = new mongoose.Schema({ name: 'string', size: 'string' }); var Tank = mongoose.model('Tank', schema);
這里,我們一定要搞清楚一個東西. 實際上, mongoose.model里面定義的第一個參數,比如’Tank’, 並不是數據庫中的, collection. 他只是collection的單數形式, 實際上在db中的collection是’Tanks’.
想兩邊名稱保持一致,可參考http://aiilive.blog.51cto.com/1925756/1405203
model 的子文檔操作
本來mongodb是沒有關系的. 但是, mongoose提供了children字段. 讓我們能夠輕松的在表間建立關系. 現在,我們來創建一個子域:
var childSchema = new Schema({ name: 'string' }); var parentSchema = new Schema({ children: [childSchema] //指明sub-doc的schema }); //在創建中指明doc var Parent = mongoose.model('Parent', parentSchema); var parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] }) parent.children[0].name = 'Matthew'; parent.save(callback);
現在, 我們就已經創建了3個table. 一個parent 包含了 兩個child 另外,如果我們想要查詢指定的doc。 則可以使用 id()方法.
var doc = parent.children.id(id);
子文檔的CRUD, 實際上就是數組的操作, 比如push,unshift,remove,pop,shift等
parent.children.push({ name: 'Liesl' });
mongoose還給移除提供了另外一個方法–remove:
var doc = parent.children.id(id).remove();
如果你忘記添加子文檔的話,可以在外圍添加, 但是字段必須在Schema中指定
var newdoc = parent.children.create({ name: 'Aaron' });
document的CRUD操作
document 的創建 關於document的創建,有兩種方法, 一種是使用document實例創建,另外一種是使用Model類創建.
document的創建
var Tank = mongoose.model('Tank', yourSchema); var small = new Tank({ size: 'small' }); //使用實例創建 small.save(function (err) { if (err) return handleError(err); // saved! }) //使用Model類創建 Tank.create({ size: 'small' }, function (err, small) { if (err) return handleError(err); // saved! })
document的查詢
Mongoose查找文檔很容易,它支持MongoDB的豐富的查詢語法。 可以使用每個models find,findById,findOne或where 等靜態方法進行查找文檔。
事實上,在mongoose中,query數據 提供了兩種方式.
- callback: 使用回調函數, 即, query會立即執行,然后返回到回調函數中.
Person.findOne({ 'name.last': 'Ghost' }, 'name occupation', function (err, person) { if (err) return handleError(err); // get data })
- query: 使用查詢方法,返回的是一個Query對象. 該對象是一個Promise, 所以可以使用 chain 進行調用.最后必須使用exec(cb)傳入回調進行處理. cb 是一個套路, 第一個參數永遠是err. 第二個就是返回的數據。
Tank.find({ size: 'small' }).where('createdDate').gt(oneYearAgo).exec(callback);
以下兩種等價寫法:
// With a 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); // Using 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);
Query Helpers
你能夠添加 query helper functions,跟定義在Schema實例方法一樣,但是返回query對象作為mongoose queries使用(說句白了就是封裝mongoose查詢方法). Query helper methods 使你能夠擴展mongoose's chainable query builder API.
animalSchema.query.byName = function(name) { return this.find({ name: new RegExp(name, 'i') }); }; var Animal = mongoose.model('Animal', animalSchema); Animal.find().byName('fido').exec(function(err, animals) { console.log(animals); });
上面4個API, 3個使用方式都是一樣的, 另外一個不同的是where. 他一樣是用來進行query. 只是,寫法和find系列略有不同.
where簡介 where的API為: Model.where(path, [val]) path實際上就是字段, 第二個參數.val表示可以用來指定,path = val的數據內容, 你也可以不寫, 交給后面進行篩選. 看一下對比demo吧:
User.find({age: {$gte: 21, $lte: 65}}, callback); //等價於: User.where('age').gte(21).lte(65).exec(callback);
從上面的query中,我們可以看到有許多fn, 比如gte,lte,$gte,$lte. 這些是db提供給我們用來查詢的快捷函數. 我們可以參考, mongoose給的參考: query Helper fn
另外還有一些游標集合的處理方法: 常用的就3個, limit,skip,sort.
limit:用來獲取限定長度的內容.
query.limit(20); //只返回前20個內容
skip: 返回,跳過指定doc后的值.
query.skip(2);
sort: 用來設置根據指定字段排序. 可以設置為1:升序, -1:降序.
query.sort({name:1,age:-1});
實際上, 關於query,我們需要了解的也就差不多了.
document刪除
reomve操作僅在通過回調時執行。 要強制執行沒有回調,您必須先調用remove(),然后使用exec()方法執行它。
我們可以在document上執行remove方法也可以在Model上。
Model.find().remove({ name: 'Anne Murray' }, callback) Model.remove({ name: 'Anne Murray' }, callback) //沒有添加回調情況 Model.find().remove({ name: 'Anne Murray' }).remove(callback) Model.remove({ name: 'Anne Murray' }).exce(callback)
document更新
使用Model.update([(conditions, doc, [options], [callback])]
不返回更新對象到應用程序。如果要更新數據庫中的單個文檔並將其返回到應用程序,請改用findOneAndUpdate。
參數說明:
- conditions: 就是query. 通過query獲取到指定doc
- doc: 就是用來替換doc內容的值.
- options: 這塊需要說一些下.
safe (boolean) 是否開啟安全模式 (default for true)
upsert (boolean) 如果沒有匹配到內容,是否自動創建 ( default for false)
multi (boolean) 如果有多個doc,匹配到,是否一起更改 ( default for false)
strict (boolean) 使用嚴格模式(default for false)
overwrite (boolean) 匹配到指定doc,是否覆蓋 (default for false)
runValidators (boolean): 表示是否用來啟用驗證. 實際上,你首先需要寫一個驗證. 關於如果書寫,驗證大家可以參考下文, validate篇(default for false)
new(使用findOneAndUpdate時才有參數):bool - 如果為true,則返回修改后的文檔而不是原始文件。 默認為false。
Model.update({age:18}, { $set: { name: 'jason borne' }}, {multi:true}, function (err, raw) { if (err) return handleError(err); console.log('raw 就是mongodb返回的更改狀態的falg ', raw); //比如: { ok: 1, nModified: 2, n: 2 } });
其中的$set是,用來指明更新的字段。
Validation
驗證器在SchemaType中定義。
Validation 是一種中間件,Mongoose 觸發 validation 同 a pre('save')鈎子一樣 。
你能夠手動觸發 validation 通過doc.validate(callback) or doc.validateSync()。
cat.save(function(error) { //自動執行,validation }); //手動觸發 validatio //上面已經設置好user的字段內容. user.validate(function(error) { //error 就是驗證不通過返回的錯誤信息 assert.equal(error.errors['phone'].message, '555.0123 is not a valid phone number!'); }); });
內置驗證器
Mongoose 有一些列內置驗證器.
- 所有的SchemaTypes都有required驗證器
- min,max: 用來給Number類型的數據設置限制.
var breakfastSchema = new Schema({ eggs: { type: Number, min: [6, 'Too few eggs'], max: 12 } });
- enum,match,maxlength,minlength: 這些驗證是給string類型的. enum 就是枚舉,表示該屬性值,只能出席那那些. match是用來匹配正則表達式的. maxlength&minlength 顯示字符串的長度.
new Schema({ drink: { type: String, enum: ['Coffee', 'Tea'] }, food:{ type: String, match:/^a/, maxlength:12, minlength:6 } })
自定義驗證器
如果內置驗證器不夠,您可以定義自定義驗證器以滿足您的需要。
// 創建驗證器 function validator (val) { return val == 'something'; } new Schema({ name: { type: String, validate: validator }}); // 附帶自定義錯誤信息 var custom = [validator, 'Uh oh, {PATH} does not equal "something".'] new Schema({ name: { type: String, validate: custom }}); //添加多驗證器 var many = [ { validator: validator, msg: 'uh oh' } , { validator: anotherValidator, msg: 'failed' } ] new Schema({ name: { type: String, validate: many }}); // 直接通過SchemaType.validate方法定義驗證器: var schema = new Schema({ name: 'string' }); schema.path('name').validate(validator, 'validation of `{PATH}` failed with value `{VALUE}`');
驗證錯誤對象
驗證失敗后返回的錯誤包含一個包含實際ValidatorError對象的錯誤對象。 每個ValidatorError都有kind,path,value和message屬性。
var toySchema = new Schema({ color: String, name: String }); var Toy = db.model('Toy', toySchema); var validator = function (value) { return /blue|green|white|red|orange|periwinkle/i.test(value); }; Toy.schema.path('color').validate(validator, 'Color `{VALUE}` not valid', 'Invalid color'); var toy = new Toy({ color: 'grease'}); toy.save(function (err) { // err is our ValidationError object // err.errors.color is a ValidatorError object assert.equal(err.errors.color.message, 'Color `grease` not valid'); assert.equal(err.errors.color.kind, 'Invalid color'); assert.equal(err.errors.color.path, 'color'); assert.equal(err.errors.color.value, 'grease'); assert.equal(err.name, 'ValidationError'); });
更新驗證器
在Model.update那一節有個參數–runValidators. 還沒有詳細說. 這里, 展開一下. 實際上, validate一般只會應用在save上, 如果你想在update使用的話, 需要額外的trick,而runValidators就是這個trick.
Mongoose還支持update()和findOneAndUpdate()操作的驗證。 在Mongoose 4.x中,更新驗證器默認關閉 - 您需要指定runValidators選項。
var opts = { runValidators: true }; Test.update({}, update, opts, function(error) { //額外開啟runValidators的驗證 // There will never be a validation error here });
更多驗證器用法請參考官方文檔
population
ongodb 本來就是一門非關系型數據庫。 但有時候,我們又需要聯合其他的table進行數據查找。 mongoose提供的 population. 用來連接多表數據查詢. 一般而言, 我們只要提供某一個collection的_id , 就可以實現完美的聯合查詢. population 用到的關鍵字是: ref 用來指明外聯的數據庫的名字. 一般,我們需要在schema中就定義好.
var mongoose = require('mongoose') , Schema = mongoose.Schema var personSchema = Schema({ _id : Number, name : String, age : Number, stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }] }); var storySchema = Schema({ _creator : { type: Number, ref: 'Person' }, title : String, fans : [{ type: Number, ref: 'Person' }] }); var Story = mongoose.model('Story', storySchema); var Person = mongoose.model('Person', personSchema);
Note: ObjectId, Number, String, and Buffer are valid for use as refs.
使用populate query方法進行關聯
Story
.findOne({ title: 'Once upon a timex.' }) .populate('_creator') .exec(function (err, story) { if (err) return handleError(err); console.log('The creator is %s', story._creator.name); // prints "The creator is Aaron" });
中間件
mongoose里的中間件,有兩個, 一個是pre, 一個是post.
- pre: 在指定方法執行之前綁定。 中間件的狀態分為 parallel和series.
- post: 相當於事件監聽的綁定
這里需要說明一下, 中間件一般僅僅只能限於在幾個方法中使用. (但感覺就已經是全部了)
- doc 方法上: init,validate,save,remove;
- model方法上: count,find,findOne,findOneAndRemove,findOneAndUpdate,update
pre
我們來看一下,pre中間件是如何綁定的.
串行
var schema = new Schema(..); schema.pre('save', function(next) { // do stuff next(); //執行完畢,執行下一中間件 });
並行
var schema = new Schema(..); // 設置第二參數為true,意味這是一個並行中間件 // as the second parameter if you want to use parallel middleware. schema.pre('save', true, function(next, done) { // calling next kicks off the next middleware in parallel next(); setTimeout(done, 100); });
post
post會在指定事件后觸發,就像事件監聽器一樣,post鈎子沒什么控制流程,即它是異步的。
schema.post('save', function(doc) { //在save完成后 觸發. console.log('%s has been saved', doc._id); });
當save方法調用時, 便會觸發post綁定的save事件.
假如你綁定了多個post。 也可以需要指定一下中間件順序.
// Takes 2 parameters: this is an asynchronous post hook schema.post('save', function(doc, next) { setTimeout(function() { console.log('post1'); // Kick off the second post hook next(); }, 10); }); // Will not execute until the first middleware calls `next()` schema.post('save', function(doc, next) { console.log('post2'); next(); })