博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Mongoose初步学习
阅读量:6583 次
发布时间:2019-06-24

本文共 18054 字,大约阅读时间需要 60 分钟。

Mongoose:优雅地在NodeJS中进行MongoDB对象建模。

我们开发Mongoose是因为(开发者)写MongoDB的验证机制、类型转换与业务逻辑模板很麻烦。

针对为应用数据建模的问题,Mongoose 提供了一套直白的,基于模式的解决方案。包括了内建的类型转换、验证器、查询构造器、业务逻辑钩子等。

Mongoose的地位是位于MongoDB与NodeJS之间的,看上去是增加了一些复杂度,但实际上却做了很多抽象,大大简化了使用MongoDB的难度。

项目安装

我们结合koa做项目展示,克隆下面项目地址

https://github.com/daly-young/mongoosebasic.git复制代码

运行:

node demos/index.js复制代码

Schema | Model | Entity

Schema : 一种以文件形式存储的数据库模型骨架,不具备数据库的操作能力

Model : 由Schema发布生成的模型,具有抽象属性和行为的数据库操作对

Entity : 由Model创建的实体,他的操作也会影响数据库

Schema、Model、Entity的关系请牢记,Schema生成Model,Model创造Entity,Model和Entity都可对数据库操作造成影响,但Model比Entity更具操作性。

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 两种隐藏的对象,

1.1---ObjectId

用Schema.Types.ObjectId 来声明一个对象ID类型。对象ID同MongoDB内置的_id 的类型,是一个24位Hash字符串。

const mongoose = require('mongoose')const ObjectId = mongoose.Schema.Types.ObjectIdconst 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)// orconst 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)  // openconst 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 Oinstancetry {    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: 如果为真,将原始结果作为返回复制代码

例子:

// STARTasync 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)})复制代码

例子:

// STARTasync 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);复制代码

转载于:https://juejin.im/post/5ad080836fb9a028d9379345

你可能感兴趣的文章
JavaScript 特殊效果代码
查看>>
【?】codeforces721E Road to Home(DP+单调队列)
查看>>
MySQL 仅保留7天、一个月数据
查看>>
Diff Two Arrays
查看>>
下拉菜单
查看>>
[清华集训2014]玛里苟斯
查看>>
Project Euler 345: Matrix Sum
查看>>
你可能不知道的技术细节:存储过程参数传递的影响
查看>>
.htaccess 基础教程(四)Apache RewriteCond 规则参数
查看>>
UVM中的class--2
查看>>
ORACLE 存储过程异常捕获并抛出
查看>>
root用户重置其他密码
查看>>
Oracle推断值为非数字
查看>>
多年前写的一个ASP.NET网站管理系统,到现在有些公司在用
查看>>
vue-cli中理不清的assetsSubDirectory 和 assetsPublicPath
查看>>
从JDK源码角度看Short
查看>>
五年 Web 开发者 star 的 github 整理说明
查看>>
Docker 部署 SpringBoot 项目整合 Redis 镜像做访问计数Demo
查看>>
中台之上(五):业务架构和中台的难点,都是需要反复锤炼出标准模型
查看>>
inno setup 打包脚本学习
查看>>