Mongoose初步學習

Mongoose:優雅地在NodeJS中進行MongoDB對象建模。html

咱們開發Mongoose是由於(開發者)寫MongoDB的驗證機制、類型轉換與業務邏輯模板很麻煩。node

針對爲應用數據建模的問題,Mongoose 提供了一套直白的,基於模式的解決方案。包括了內建的類型轉換、驗證器、查詢構造器、業務邏輯鉤子等。git

Mongoose的地位是位於MongoDB與NodeJS之間的,看上去是增長了一些複雜度,但實際上卻作了不少抽象,大大簡化了使用MongoDB的難度。github

項目安裝

咱們結合koa作項目展現,克隆下面項目地址mongodb

https://github.com/daly-young/mongoosebasic.git
複製代碼

運行:數據庫

node demos/index.js
複製代碼

Schema | Model | Entity

Schema : 一種以文件形式存儲的數據庫模型骨架,不具有數據庫的操做能力api

Model : 由Schema發佈生成的模型,具備抽象屬性和行爲的數據庫操做對數組

Entity : 由Model建立的實體,他的操做也會影響數據庫安全

Schema、Model、Entity的關係請牢記,Schema生成Model,Model創造Entity,Model和Entity均可對數據庫操做形成影響,但Model比Entity更具操做性。app

Schema

schema是mongoose裏會用到的一種數據模式,能夠理解爲表結構的定義;每一個schema會映射到mongodb中的一個collection,它不具有操做數據庫的能力

在根目錄建models文件夾,咱們定義一個user的Schema,命名爲user.js

const UserSchema = new mongoose.Schema({
    userName: String
})
複製代碼

定義一個Schema就這麼簡單,指定字段名和類型。

1---Schema.Type

Schema.Type是由Mongoose內定的一些數據類型,基本數據類型都在其中,它也內置了一些Mongoose特有的Schema.Type。固然,你也能夠自定義Schema.Type,只有知足Schema.Type的類型才能定義在Schema內。

Schema Types內置類型以下: String, Number, Boolean | Bool, Array, Buffer, Date, ObjectId, Mixed

1.0---Buffer

Buffer 類的實例相似於整數數組,但 Buffer 的大小是固定的、且在 V8 堆外分配物理內存。 Buffer 的大小在被建立時肯定,且沒法調整。 Buffer 類是一個全局變量類型,用來直接處理二進制數據的。 它可以使用多種方式構建。

Buffer 和 ArrayBuffer 是 Nodejs 兩種隱藏的對象,相關內容請查看 NodeJS-API

1.1---ObjectId

用Schema.Types.ObjectId 來聲明一個對象ID類型。對象ID同MongoDB內置的_id 的類型,是一個24位Hash字符串。

const mongoose = require('mongoose')
const ObjectId = mongoose.Schema.Types.ObjectId
const Car = new Schema({ driver: ObjectId })
複製代碼

1.2---Mixed

混合型是一種「存啥都行」的數據類型,它的靈活性來自於對可維護性的妥協。Mixed類型用Schema.Types.Mixed 或者一個字面上的空對象{}來定義。下面的定義是等價的:

const AnySchema = new Schema({any:{}})
const AnySchema = new Schema({any:Schema.Types.Mixed})
複製代碼

混合類型由於沒有特定約束,所以能夠任意修改,一旦修改了原型,則必須調用markModified()

person.anything = {x:[3,4,{y:'change'}]}
person.markModified('anything') // 輸入值,意味着這個值要改變
person.save(); // 改變值被保存
複製代碼

2---Validation

數據的存儲是須要驗證的,不是什麼數據都能往數據庫裏丟或者顯示到客戶端的,數據的驗證須要記住如下規則:

  • 驗證始終定義在SchemaType中
  • 驗證是一個內部中間件
  • 驗證是在一個Document被保存時默認啓用的,除非你關閉驗證
  • 驗證是異步遞歸的,若是你的SubDoc驗證失敗,Document也將沒法保存
  • 驗證並不關心錯誤類型,而經過ValidationError這個對象能夠訪問

2.1---驗證器 ####=

required 非空驗證 min/max 範圍驗證(邊值驗證) enum/match 枚舉驗證/匹配驗證 validate 自定義驗證規則

如下是綜合案例:

var PersonSchema = new Schema({
  name:{
    type:'String',
    required:true //姓名非空
  },
  age:{
    type:'Nunmer',
    min:18,       //年齡最小18
    max:120     //年齡最大120
  },
  city:{
    type:'String',
    enum:['北京','上海']  //只能是北京、上海人
  },
  other:{
    type:'String',
    validate:[validator,err]  //validator是一個驗證函數,err是驗證失敗的錯誤信息
  }
});
複製代碼

2.2---驗證失敗

若是驗證失敗,則會返回err信息,err是一個對象該對象屬性以下

err.errors                //錯誤集合(對象)
err.errors.color          //錯誤屬性(Schema的color屬性)
err.errors.color.message  //錯誤屬性信息
err.errors.path             //錯誤屬性路徑
err.errors.type             //錯誤類型
err.name                //錯誤名稱
err.message                 //錯誤消息
複製代碼

一旦驗證失敗,Model和Entity都將具備和err同樣的errors屬性

3---配置項

在使用new Schema(config)時,咱們能夠追加一個參數options來配置Schema的配置,例如:

const ExampleSchema = new Schema(config,options)

// or

const ExampleSchema = new Schema(config)
ExampleSchema.set(option,value)
複製代碼

Options:

  • autoIndex: bool - defaults to null (which means use the connection's autoIndex option)
  • bufferCommands: bool - defaults to true
  • capped: bool - defaults to false
  • collection: string no default
  • id: bool defaults to true
  • _id: bool defaults to true
  • minimize: bool controls document#toObject behavior when called manually defaults to true
  • read: string
  • safe: bool defaults to true.
  • shardKey: bool defaults to null
  • strict: bool defaults to true
  • toJSON object no default
  • toObject object no default
  • typeKey string defaults to 'type'
  • useNestedStrict boolean defaults to false
  • validateBeforeSave bool defaults to true
  • versionKey: string defaults to "__v"
  • collation: object defaults to null (which means use no collation)

詳見官方文檔

3.1---safe——安全屬性(默認安全)

通常可作以下配置:

new Schema({...},{safe:true})
複製代碼

固然咱們也能夠這樣

new Schema({...},{safe:{j:1,w:2,wtimeout:10000}})
複製代碼

j表示作1份日誌,w表示作2個副本(尚不明確),超時時間10秒

3.2---strict——嚴格配置(默認啓用)

默認是enabled,確保Entity的值存入數據庫前會被自動驗證,若是實例中的域(field)在schema中不存在,那麼這個域不會被插入到數據庫。 若是你沒有充足的理由,請不要停用,例子:

const ThingSchema = new Schema({a:String})
const ThingModel = db.model('Thing',SchemaSchema)
const thing = new ThingModel({iAmNotInTheThingSchema:true})
thing.save() // iAmNotInTheThingSchema will not be saved
複製代碼

若是取消嚴格選項,iAmNotInTheThingSchema將會被存入數據庫

該選項也能夠在構造實例時使用,例如:

const ThingModel = db.model('Thing')
const thing1 = new ThingModel(doc,true)  // open
const thing2 = new ThingModel(doc,false) // close
複製代碼

注意:strict也能夠設置爲throw,表示出現問題將會拋出錯誤

3.3---capped——上限設置

若是有數據庫的批量操做,該屬性能限制一次操做的量,例如:

new Schema({...},{capped:1024})  // can operate 1024 at most once
複製代碼

固然該參數也但是JSON對象,包含size、max、autiIndexId屬性

new Schema({...},{capped:{size:1024,max:100,autoIndexId:true}})
複製代碼

3.4---versionKey——版本鎖

版本鎖是Mongoose默認配置(__v屬性)的,若是你想本身定製,以下:

new Schema({...},{versionKey:'__someElse'});
複製代碼

此時存入數據庫的版本鎖就不是__v屬性,而是__someElse,至關因而給版本鎖取名字。 具體怎麼存入都是由Mongoose和MongoDB本身決定,固然,這個屬性你也能夠去除。

new Schema({...},{versionKey:false});
複製代碼

除非你知道你在作什麼,而且你知道這樣作的後果

3.5--- autoIndex——自動索引

應用開始的時候,Mongoose對每個索引起送一個ensureIndex的命令。索引默認(_id)被Mongoose建立。

當咱們不須要設置索引的時候,就能夠經過設置這個選項。

const schema = new Schema({..}, { autoIndex: false }) const Clock = mongoose.model('Clock', schema) Clock.ensureIndexes(callback)

4---Schema的擴展

4.1 實例方法

有的時候,咱們創造的Schema不只要爲後面的Model和Entity提供公共的屬性,還要提供公共的方法。

下面例子比快速通道的例子更加高級,能夠進行高級擴展:

const schema = new Schema({
    name: String,
    type: String
})
// 檢查類似數據
schema.methods.findSimilarTypes = () => {
    return mongoose.model('Oinstance').find({ type: 'engineer' })
}
const Oinstance = mongoose.model('Oinstance', schema)
module.exports = Oinstance
複製代碼

使用以下:

const Oinstance = require('../models/06instance-method')
const m = new Oinstance
try {
    let res = await m.findSimilarTypes()
    ctx.body = res
} catch (e) {
    console.log('!err==', e)
    return next
}
複製代碼

4.2 靜態方法

靜態方法在Model層就能使用,以下:

const schema = new Schema({
    name: String,
    type: String
})

schema.statics.findSimilarTypes = () => {
    return mongoose.model('Ostatic').find({ type: 'engineer' })
}

// 例子
const Ostatic = mongoose.model('Ostatic', schema)

module.exports = Ostatic
複製代碼

使用以下: try { let res = await Ostatic.findSimilarTypes() ctx.body = res } catch (e) { console.log('!err==', e) return next }

methods和statics的區別

區別就是一個給Model添加方法(statics),一個給實例添加方法(methods)

4.3 虛擬屬性

Schema中若是定義了虛擬屬性,那麼該屬性將不寫入數據庫,例如:

const PersonSchema = new Schema({
	name:{
		first:String,
		last:String
  	}
})
const PersonModel = mongoose.model('Person',PersonSchema)
const daly = new PersonModel({
	name:{first:'daly',last:'yang'}
})
複製代碼

若是每次想使用全名就得這樣

console.log(daly.name.first + ' ' + daly.name.last);
複製代碼

顯然這是很麻煩的,咱們能夠定義虛擬屬性:

PersonSchema.virtual('name.full').get(function(){
  return this.name.first + ' ' + this.name.last;
});
複製代碼

  那麼就能用daly.name.full來調用全名了,反之若是知道full,也能夠反解first和last屬性

PersonSchema.virtual('name.full').set(function(name){
  var split = name.split(' ');
  this.name.first = split[0];
  this.name.last = split[1];
});
var PersonModel = mongoose.model('Person',PersonSchema);
var krouky = new PersonModel({});
krouky.name.full = 'daly yang';
console.log(krouky.name.first);
複製代碼

Model

1---什麼是Model

Model模型,是通過Schema構造來的,除了Schema定義的數據庫骨架之外,還具備數據庫行爲模型,他至關於管理數據庫屬性、行爲的類。

實際上,Model纔是操做數據庫最直接的一塊內容. 咱們全部的CRUD就是圍繞着Model展開的。

2---如何建立Model

  你必須經過Schema來建立,以下:

const TankSchema = new Schema({
  name:'String',
  size:'String' 
})
const TankModel = mongoose.model('Tank',TankSchema)
複製代碼

3---操做Model

該模型就能直接拿來操做,具體查看API,例如:

const tank = {'something',size:'small'}
TankModel.create(tank)
複製代碼

注意:

你可使用Model來建立Entity,Entity實體是一個特有Model具體對象,可是他並不具有Model的方法,只能用本身的方法。

const tankEntity = new TankModel('someother','size:big');
tankEntity.save()
複製代碼

實例

增長

  • save()
  • create()
  • insertOne() 插入單條數據
  • insertMany() 比create方法快,由於是多條數據一次操做

若是是Entity,使用save方法,若是是Model,使用create方法

module.exports = {
    async mCreateModal(ctx, next) {
        let result = {
            success: false,
            code: 0,
            resultDes: ""
        }
        let param = ctx.request.body
        try {
			// Modal建立
            let data = await Ocrud.create(param)
            result.success = true
            result.data = data
            ctx.body = result
        } catch (e) {
            console.log('!err==', e)
            result.code = -1
            result.resultDes = e
            ctx.body = result
            return next
        }
    },
    async mCreateEntity(ctx, next) {
        let result = {
            success: false,
            code: 0,
            resultDes: ""
        }
        let param = ctx.request.body
        const user = new Ocrud(param)
        try {
			// Entity建立
            let data = await user.save()
            result.success = true
            result.data = data
            ctx.body = result
        } catch (e) {
            console.log('!err==', e)
            result.code = -2
            result.resultDes = e
            ctx.body = result
            return next
        }
    },
	async mInsertMany(ctx, next) {
        let result = {
            success: false,
            code: 0,
            resultDes: ""
        }
        let param = ctx.request.users
        try {
            let data = await user.insertMany(param)
            result.success = true
            result.data = data
            ctx.body = result
        } catch (e) {
            console.log('!err==', e)
            result.code = -2
            result.resultDes = e
            ctx.body = result
            return next
        }
    },
}
複製代碼

更新

有三種方式來更新數據:

  1. update 該方法會匹配到所查找的內容進行更新,不會返回數據
  2. updateone 一次更新一條
  3. updateMany 一次更新多條
  4. findOneAndUpdate 該方法會根據查找去更新數據庫,另外也會返回查找到的並未改變的數據
  5. findByIdAndUpdate 該方法跟上面的findOneAndUpdate方法功能同樣,不過他是根據ID來查找文檔並更新的

三個方法都包含四個參數,稍微說明一下幾個參數的意思:

Model.update(conditions, doc, [options], [callback])
複製代碼

conditions:查詢條件
update:更新的數據對象,是一個包含鍵值對的對象
options:是一個聲明操做類型的選項,這個參數在下面再詳細介紹
callback:回調函數

options

safe (boolean): 默認爲true。安全模式
upsert (boolean): 默認爲false。若是不存在則建立新記錄
multi (boolean): 默認爲false。是否更新多個查詢記錄
runValidators: 若是值爲true,執行Validation驗證
setDefaultsOnInsert: 若是upsert選項爲true,在新建時插入文檔定義的默認值
strict (boolean): 以strict模式進行更新
overwrite (boolean): 默認爲false。禁用update-only模式,容許覆蓋記錄
複製代碼

對於options參數,在update方法中和findOneAndUpdate、findByIdAndUpdate兩個方法中的可選設置是不一樣的;

在update方法中,options的可選設置爲:

{
	safe:true|false, //聲明是否返回錯誤信息,默認true
	upsert:false|true, //聲明若是查詢不到須要更新的數據項,是否須要新插入一條記錄,默認false
	multi:false|true, //聲明是否能夠同時更新多條記錄,默認false
	strict:true|false //聲明更新的數據中是否能夠包含在schema定義以外的字段數據,默認true
}
複製代碼

findOneAndUpdate,options可選設置項爲:

new: bool - 默認爲false。返回修改後的數據。
upsert: bool - 默認爲false。若是不存在則建立記錄。
fields: {Object|String} - 選擇字段。相似.select(fields).findOneAndUpdate()。
maxTimeMS: 查詢用時上限。
sort: 若是有多個查詢條件,按順序進行查詢更新。
runValidators: 若是值爲true,執行Validation驗證。
setDefaultsOnInsert: 若是upsert選項爲true,在新建時插入文檔定義的默認值。
rawResult: 若是爲真,將原始結果做爲回調函數第三個參數。
複製代碼

findByIdAndUpdate,options可選設置項爲:

new: bool - 默認爲false。返回修改後的數據。
upsert: bool - 默認爲false。若是不存在則建立記錄。
runValidators: 若是值爲true,執行Validation驗證。
setDefaultsOnInsert: 若是upsert選項爲true,在新建時插入文檔定義的默認值。
sort: 若是有多個查詢條件,按順序進行查詢更新。
select: 設置返回的數據字段
rawResult: 若是爲真,將原始結果做爲返回
複製代碼

例子:

// START
async mUpdate(ctx, next) {
    let result = {
        success: false,
        code: 0,
        resultDes: ""
    }
    let condition = ctx.request.body.condition
    let doc = ctx.request.body.doc
    console.log(condition, '===condition')
    console.log(doc, '===doc')
    try {
        let data = await Ocrud.update(condition, doc, { multi: true })
        result.success = true
        result.data = data
        ctx.body = result
    } catch (e) {
        console.log('!er==', e)
        result.code = -3
        result.resultDes = e
        ctx.body = result
        return next
    }
},
async mUpdateOne(ctx, next) {
    let result = {
        success: false,
        code: 0,
        resultDes: ""
    }
    let condition = ctx.request.body.condition
    let doc = ctx.request.body.doc
    try {
        let data = await Ocrud.updateOne(condition, doc)
        result.success = true
        result.data = data
        ctx.body = result
    } catch (e) {
        console.log('!er==', e)
        result.code = -3
        result.resultDes = e
        ctx.body = result
        return next
    }
},
async mUpdateMany(ctx, next) {
    let result = {
        success: false,
        code: 0,
        resultDes: ""
    }
    let condition = ctx.request.body.condition
    let doc = ctx.request.body.doc
    try {
        let data = await Ocrud.updateMany(condition, doc, { multi: true })
        result.success = true
        result.data = data
        ctx.body = result
    } catch (e) {
        console.log('!er==', e)
        result.code = -3
        result.resultDes = e
        ctx.body = result
        return next
    }
},
async mFindOneAndUpdate(ctx, next) {
    let result = {
        success: false,
        code: 0,
        resultDes: ""
    }
    let condition = ctx.request.body.condition
    let doc = ctx.request.body.doc
    try {
        let data = await Ocrud.findOneAndUpdate(condition, doc, { new: true, rawResult: true })
        result.success = true
        result.data = data
        ctx.body = result
    } catch (e) {
        console.log('!er==', e)
        result.code = -3
        result.resultDes = e
        ctx.body = result
        return next
    }
},
async mFindByIdAndUpdate(ctx, next) {
    let result = {
        success: false,
        code: 0,
        resultDes: ""
    }
    let _id = ctx.request.body.id
    let doc = ctx.request.body.doc
    try {
        let data = await Ocrud.findByIdAndUpdate(_id, doc)
        result.success = true
        result.data = data
        ctx.body = result
    } catch (e) {
        console.log('!er==', e)
        result.code = -3
        result.resultDes = e
        ctx.body = result
        return next
    }
},
// END 
複製代碼

刪除

  • remove() 刪除全部符合條件的文檔,若是隻想刪除第一個符合條件的對象,能夠添加設置single爲true
  • delete() 刪除第一個符合對象的文檔,會忽視single的值
  • deleteMany() 刪除全部符合條件的文檔,會忽視single的值
  • findOneAndRemove()
  • findByIdAndRemove()

remove方法有兩種使用方式,一種是用在模型上,另外一種是用在模型實例上,例如:

User.remove({ name : /Simon/ } , function (err){
  if (!err){
    // 刪除名字中包含simon的全部用戶
  }
});

User.findOne({ email : 'simon@theholmesoffice.com'},function (err,user){
  if (!err){
    user.remove( function(err){
      // 刪除匹配到該郵箱的第一個用戶
    })
  }
})
複製代碼

接下來看一下findOneAndRemove方法: sort: 若是有多個查詢條件,按順序進行查詢更新 maxTimeMS: 查詢用時上限 requires mongodb >= 2.6.0 select: 設置返回的數據字段 rawResult: 若是爲真,將原始結果返回

User.findOneAndRemove({name : /Simon/},{sort : 'lastLogin', select : 'name email'},function (err, user){
  if (!err) {
    console.log(user.name + " removed");
    // Simon Holmes removed
  }
})
複製代碼

另一個findByIdAndRemove方法則是一模一樣的。 sort: 若是有多個查詢條件,按順序進行查詢更新 select: 設置返回的數據字段 rawResult: 若是爲真,將原始結果返回

User.findByIdAndRemove(req.body._id,function (err, user) {
  if(err){
    console.log(err)
    return
  }
  console.log("User deleted:", user)
})
複製代碼

例子:

// START
async mDelete(ctx, next) {
    let result = {
        success: false,
        code: 0,
        resultDes: ""
    }
    let param = ctx.request.body.condition
    try {
        let data = await Ocrud.delete(param)
        result.success = true
        result.data = data
        ctx.body = result
    } catch (e) {
        console.log('!er==', e)
        result.code = -3
        result.resultDes = e
        ctx.body = result
        return next
    }
},
async mRemove(ctx, next) {
    let result = {
        success: false,
        code: 0,
        resultDes: ""
    }
    let param = ctx.request.body.condition
    try {
        let data = await Ocrud.remove(param)
        result.success = true
        result.data = data
        ctx.body = result
    } catch (e) {
        console.log('!er==', e)
        result.code = -3
        result.resultDes = e
        ctx.body = result
        return next
    }
},
async mDeleteMany(ctx, next) {
    let result = {
        success: false,
        code: 0,
        resultDes: ""
    }
    let param = ctx.request.body.condition
    try {
        let data = await Ocrud.deleteMany(param)
        result.success = true
        result.data = data
        ctx.body = result
    } catch (e) {
        console.log('!er==', e)
        result.code = -3
        result.resultDes = e
        ctx.body = result
        return next
    }
},
async mFindOneAndRemove(ctx, next) {
    let result = {
        success: false,
        code: 0,
        resultDes: ""
    }
    let param = ctx.request.body.condition
    try {
        let data = await Ocrud.findOneAndRemove(param)
        result.success = true
        result.data = data
        ctx.body = result
    } catch (e) {
        console.log('!er==', e)
        result.code = -3
        result.resultDes = e
        ctx.body = result
        return next
    }
},
async mFindByIdAndRemove(ctx, next) {
    let result = {
        success: false,
        code: 0,
        resultDes: ""
    }
    let param = ctx.request.body.id
    try {
        let data = await Ocrud.findByIdAndRemove(param)
        result.success = true
        result.data = data
        ctx.body = result
    } catch (e) {
        console.log('!er==', e)
        result.code = -3
        result.resultDes = e
        ctx.body = result
        return next
    }
},
// END 
複製代碼

綜合寫法

  • bulkWrite() 能夠一次發送insertOne, updateOne, updateMany, replaceOne, deleteOne, and/or deleteMany多種操做命令,比單條命令一次發送效率要高

Character.bulkWrite([
  {
    insertOne: {
      document: {
        name: 'Eddard Stark',
        title: 'Warden of the North'
      }
    }
  },
  {
    updateOne: {
      filter: { name: 'Eddard Stark' },
      // If you were using the MongoDB driver directly, you'd need to do
      // `update: { $set: { title: ... } }` but mongoose adds $set for
      // you.
      update: { title: 'Hand of the King' }
    }
  },
  {
    deleteOne: {
      {
        filter: { name: 'Eddard Stark' }
      }
    }
  }
]).then(handleResult)
複製代碼

Query

Query構造函數被用來構建查詢,不需直接實例化Query,可使用MOdel函數像 MOdel.find()

const query = MyModel.find(); // `query` is an instance of `Query`
query.setOptions({ lean : true });
query.collection(model.collection);
query.where('age').gte(21).exec(callback);

// You can instantiate a query directly. There is no need to do
// this unless you're an advanced user with a very good reason to.
const query = new mongoose.Query();
複製代碼

鏈式查詢

由於query的操做始終返回自身,咱們能夠採用更形象的鏈式寫法

query
  .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);
複製代碼
相關文章
相關標籤/搜索