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 : 一种以文件形式存储的数据库模型骨架,不具有数据库的操做能力api
Model : 由Schema发布生成的模型,具备抽象属性和行为的数据库操做对数组
Entity : 由Model建立的实体,他的操做也会影响数据库安全
Schema、Model、Entity的关系请牢记,Schema生成Model,Model创造Entity,Model和Entity均可对数据库操做形成影响,但Model比Entity更具操做性。app
schema是mongoose里会用到的一种数据模式,能够理解为表结构的定义;每一个schema会映射到mongodb中的一个collection,它不具有操做数据库的能力
在根目录建models文件夹,咱们定义一个user的Schema,命名为user.js
const UserSchema = new mongoose.Schema({
userName: String
})
复制代码
定义一个Schema就这么简单,指定字段名和类型。
Schema.Type是由Mongoose内定的一些数据类型,基本数据类型都在其中,它也内置了一些Mongoose特有的Schema.Type。固然,你也能够自定义Schema.Type,只有知足Schema.Type的类型才能定义在Schema内。
Schema Types内置类型以下: String, Number, Boolean | Bool, Array, Buffer, Date, ObjectId, Mixed
Buffer 类的实例相似于整数数组,但 Buffer 的大小是固定的、且在 V8 堆外分配物理内存。 Buffer 的大小在被建立时肯定,且没法调整。 Buffer 类是一个全局变量类型,用来直接处理二进制数据的。 它可以使用多种方式构建。
Buffer 和 ArrayBuffer 是 Nodejs 两种隐藏的对象,相关内容请查看 NodeJS-API
用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 })
复制代码
混合型是一种“存啥都行”的数据类型,它的灵活性来自于对可维护性的妥协。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(); // 改变值被保存
复制代码
数据的存储是须要验证的,不是什么数据都能往数据库里丢或者显示到客户端的,数据的验证须要记住如下规则:
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是验证失败的错误信息
}
});
复制代码
若是验证失败,则会返回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属性
在使用new Schema(config)时,咱们能够追加一个参数options来配置Schema的配置,例如:
const ExampleSchema = new Schema(config,options)
// or
const ExampleSchema = new Schema(config)
ExampleSchema.set(option,value)
复制代码
Options:
通常可作以下配置:
new Schema({...},{safe:true})
复制代码
固然咱们也能够这样
new Schema({...},{safe:{j:1,w:2,wtimeout:10000}})
复制代码
j表示作1份日志,w表示作2个副本(尚不明确),超时时间10秒
默认是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,表示出现问题将会抛出错误
若是有数据库的批量操做,该属性能限制一次操做的量,例如:
new Schema({...},{capped:1024}) // can operate 1024 at most once
复制代码
固然该参数也但是JSON对象,包含size、max、autiIndexId属性
new Schema({...},{capped:{size:1024,max:100,autoIndexId:true}})
复制代码
版本锁是Mongoose默认配置(__v属性)的,若是你想本身定制,以下:
new Schema({...},{versionKey:'__someElse'});
复制代码
此时存入数据库的版本锁就不是__v属性,而是__someElse,至关因而给版本锁取名字。 具体怎么存入都是由Mongoose和MongoDB本身决定,固然,这个属性你也能够去除。
new Schema({...},{versionKey:false});
复制代码
除非你知道你在作什么,而且你知道这样作的后果
应用开始的时候,Mongoose对每个索引起送一个ensureIndex的命令。索引默认(_id)被Mongoose建立。
当咱们不须要设置索引的时候,就能够经过设置这个选项。
const schema = new Schema({..}, { autoIndex: false }) const Clock = mongoose.model('Clock', schema) Clock.ensureIndexes(callback)
有的时候,咱们创造的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
}
复制代码
静态方法在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)
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模型,是通过Schema构造来的,除了Schema定义的数据库骨架之外,还具备数据库行为模型,他至关于管理数据库属性、行为的类。
实际上,Model才是操做数据库最直接的一块内容. 咱们全部的CRUD就是围绕着Model展开的。
你必须经过Schema来建立,以下:
const TankSchema = new Schema({
name:'String',
size:'String'
})
const TankModel = mongoose.model('Tank',TankSchema)
复制代码
该模型就能直接拿来操做,具体查看API,例如:
const tank = {'something',size:'small'}
TankModel.create(tank)
复制代码
注意:
你可使用Model来建立Entity,Entity实体是一个特有Model具体对象,可是他并不具有Model的方法,只能用本身的方法。
const tankEntity = new TankModel('someother','size:big');
tankEntity.save()
复制代码
若是是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
}
},
}
复制代码
有三种方式来更新数据:
三个方法都包含四个参数,稍微说明一下几个参数的意思:
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方法有两种使用方式,一种是用在模型上,另外一种是用在模型实例上,例如:
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
复制代码
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,可使用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);
复制代码