Node.js操作MongoDB

1 Native Driver (官方驱动)

1.1 安装

npm install mongodb --save

1.2 连接MongoDB服务

示例

//导入模块
const MongoClient = require('mongodb').MongoClient;

// 定义 连接url
const url = 'mongodb://localhost:27017';

// 定义数据库
const dbName = 'myproject';

// 连接MongoDB服务器
MongoClient.connect(url, function(err, client) {
  if (err) throw err;
  
  //选择数据库
  const db = client.db(dbName);

  client.close();
});

连接需要验证的服务

//导入模块
const MongoClient = require('mongodb').MongoClient;
const util = require('util');

//声明用户名 密码
const user = encodeURIComponent('dave');
const password = encodeURIComponent('abc123');

// 定义 URL
const url = util.format('mongodb://%s:%s@localhost:27017/',
  user, password);

// 连接服务
MongoClient.connect(url, function(err, client) {
  if (err) throw err;
  
  //选择数据库
  const db = client.db(dbName);
    
  client.close();
});

1.3 集合操作

创建集合

db.createCollection(coolName, { "capped": true, "size": 100000, "max": 5000},
    function(err, results) {
      if (err) throw err;
      //results 是表示集合的对象
      console.log("集合创建成功.");
    }
);

删除集合

db.dropCollection(coolName, (err, result) => {
    if (err) throw err;
    console.log('删除成功');
})

1.4 CURD 增删改查 (Create Update Retrieve Delete)

1.4.1 添加文档

// 添加一条文档
db.collection('collName').insertOne({a:1}, function(err, r) {
    if (err) throw err;
    console.log(r.insertedCount);
});

// 添加多条文档
db.collection('collName').insertMany([{a:2}, {a:3}], function(err, r) {
    if (err) throw err;
    console.log(r.insertedCount);
});
  • MongoDB 会自动创建集合

1.4.2 更新文档

const col = db.collection('collName');

//更新一条文档
col.updateOne({a:3}, {$set: {b: 1}}, {
     upsert: true
 }, function(err, r) {
     if (err) throw err;
     console.log(r.matchedCount);
     console.log(r.upsertedCount);
 });

//更新多条文档
  col.updateMany({a:2}, {$set: {b: 1}}, function(err, r) {
        if (err) throw err;
        console.log(r.matchedCount);
        console.log(r.modifiedCount);
  })

1.4.3 删除文档

const col = db.collection('collName');

//删除一条文档
col.deleteOne({a:1}, function(err, r) {
    if (err) throw err;
    console.log(r.deletedCount);
});

// 删除多条文档
col.deleteMany({a:2}, function(err, r) {
    if(err) throw err;
    console.log(r.deletedCount);
});

1.4.4 查询文档

const col = db.collection('collName');

//查询满足条件的一条数据
col.findOne({a:100}, function(err, result) { // 返回集合中所有数据
    if (err) throw err;
    console.log(result);
});

//查询满足条件的所有数据
col.find({}).toArray(function(err, result) { // 返回集合中所有数据
    if (err) throw err;
    console.log(result);
});

//查询并修改
col.findOneAndUpdate({a:1}, {$set: {b: 1}}, {
    returnOriginal: false
    , sort: [[a,1]]
    , upsert: true
}, function(err, r) {
    if (err) throw err;
    console.log(r.value); //r.value是修改前的文档
})
    
//查询并且删除
col.findOneAndDelete({a:2}, function(err, r) {
    if (err) throw err;
    console.log(r.value); //返回被删掉的文档
});

//排序
col.find().sort().toArray()

//数量限制
col.find().skip().limit().toArray()
col.find().limit().toArray()

2 mongoose

2.1 安装以及使用mongoose 连接MongoDB服务

关于mongoose

安装和导入

npm install mongoose --save
const mongoose = request('mongoose');

连接MongoDB

//语法
mongoose.connect(uri, options, (err) => {
    
});
//或者
mongoose.connect(uri options).then(() => {
    
}, (err) => {
    
});
  • options 选项

    bufferCommands 是否启用缓存

    user 用户名

    pass 密码

    dbName 数据库名

    autoReconnect 自动重新连接

    autoIndex 自动创建索引

  • uri

    mongodb://localhost/myapp
    mongodb://username:password@host:port/database?options...
    

connection 对像

  • db
  • host
  • port
  • user
  • pass
  • createCollection() 创建集合
  • dropCollection() 删除集合
  • dropDatabase() 删除数据库

2.2 mongoose 基本概念

Schema

Mongoose的一切都始于一个Schema。每个schema映射到MongoDB的集合(collection)和定义该集合(collection)中的文档的形式。 Schemas不仅定义了文档和属性的结构,还定义了文档实例方法、静态模型方法、复合索引和文档被称为中间件的生命周期钩子。

var blogSchema = new Schema({
  title:  String,
  author: String,
  body:   String,
  comments: [{ body: String, date: Date }],
  date: { type: Date, default: Date.now },
  hidden: Boolean,
  meta: {
    votes: Number,
    favs:  Number
  }
});

Schema的类型

  • String
  • Number
  • Date
  • Buffer
  • Boolean
  • Mixed
  • ObjectId
  • Array
  • Map
  • Decimal128

Model

使用schema定义,我们需要schema转成可以用的模型

var Blog = mongoose.model('Blog', blogSchema);
  • 查询 Model.find()
  • 删除 remove()
  • 更新 update()

Document

  • Document对象是Model的实例,表示集合中的一个文档
  • save() 保存

2.3 mongoose CURD

模块

  • http
  • ejs
  • mongoose
  • url
  • formidable

目录结构

|-- views 模板目录
|---- index.ejs
|---- add.ejs
|---- edit.ejs
|-- app.js  
|-- article.model.js  

2.4 Schema

索引

在 mongoose 中,我们在 Schema 中定义索引

实例方法

// 定义一个 schema
const kittySchema = mongoose.Schema({
  name: String
});

// 为 事例 添加一个方法
kittySchema.methods.speak = function () {
  var greeting = this.name
    ? "Meow name is " + this.name
    : "I don't have a name";
  console.log(greeting);
}
  • 重写 mongoose 的默认方法会造成无法预料的结果
  • 不要在自定义方法中使用 ES6 箭头函数,会造成 this 指向错误。

静态方法

// assign a function to the "statics" object of our animalSchema
animalSchema.statics.findByName = function(name, cb) {
   return this.find({ name: new RegExp(name, 'i') }, cb);
};

var Animal = mongoose.model('Animal', animalSchema);
Animal.findByName('fido', function(err, animals) {
   console.log(animals);
});
  • 不要在静态方法中使用 ES6 箭头函数

虚拟值

personSchema.virtual('fullName').get(function () {
  return this.name.first + ' ' + this.name.last;
});

别名

var personSchema = new Schema({
  n: {
    type: String,
    // Now accessing `name` will get you the value of `n`, and setting `n` will set the value of `name`
    alias: 'name'
  }
});

选项

  • autoIndex 应用启动时,Mongoose 自动发送 createIndex 指令, schema 里的每个 index 都会被创建
  • bufferCommands
  • capped 设置集合的容量
  • collection 自定义集合名称
  • id Mongoose 会默认生成一个虚拟值 id,指向文档的 _id 字段。 如果你不需要 id 虚拟值,可以通过这个选项禁用此功能。
  • _id Mongoose 默认给你的 Schema 赋值一个 _id。 这个值的类型是 ObjectId,这与MongoDB的默认表现一致。 如果你不需要 _id,可以通过这个选项禁用此功能。
  • minimize Mongoose 默认不保存空对象。 如果把 minimize 选项设为 false,Mongoose 将保存空对象。
  • strict Strict 选项默认为 true,这意味着你不能 save schema 里没有声明的属性。
  • versionKey versionKey 是 Mongoose 在文件创建时自动设定的。 这个值包含文件的内部修订号。 versionKey 是一个字符串,代表版本号的属性名, 默认值为 __v。如果这个值与你的计划冲突,你可以设定为其他名称
  • timestamps 如果设置了 timestamps 选项, mongoose 会在你的 schema 自动添加 createdAt 和 updatedAt 字段, 其类型为 Date。 这两个字段的默认名是 createdAt 和 updatedAt, 你可以通过设定 timestamps.createdAt 和 timestamps.updatedAt 自定义字段名称

2.5 查询

查询方法

  • model.find()
  • model.findOne()
  • model.findById()
  • model.distinct()
  • model.count()
  • Model.findOneAndDelete()
  • Model.findOneAndRemove()
  • Model.findOneAndUpdate()
  • Model.findByIdAndDelete()
  • Model.findByIdAndRemove()
  • Model.findByIdAndUpdate()
  • 支持回调和promise的用法

Query 对象 (链式调用)

以上查询方法,返回的是Query对象

查询条件
  • where()
  • equals() / ne()
  • gt() / gte()
  • lt() / lte()
  • in() / nin()
  • regex()
  • and
  • or
指定字段
  • select()
数量限制
  • skip()
  • limit()
排序
  • sort()
执行
  • exec()

其他增删改方法

  • model.create()
  • model.update()
  • model.updateOne()
  • model.updateMany()
  • model.replaceOne()
  • model.remove()
  • model.deleteOne()
  • model.deleteMany()

2.8 Aggregate

aggregate表达式

  • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。对应project()方法
  • match:用于过滤数据,只输出符合条件的文档。match使用MongoDB的标准查询操作。对应match()。
  • $limit:用来限制MongoDB聚合管道返回的文档数。对应limit()方法
  • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。对应skip()。
  • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。对应unwind()方法
  • $group:将集合中的文档分组,可用于统计结果。对应group()方法
  • $sort:将输入文档排序后输出。对应sort()方法
  • $geoNear:输出接近某一地理位置的有序文档。对应near()。
  • $sample:随机选择N个
  • $lookup:连接操作符,用于连接同一个数据库中另一个集合,并获取指定的文档,类似于populate

$group 表达式

  • $sum 计算总和。
  • $avg 计算平均值
  • $min 获取集合中所有文档对应值得最小值。
  • $max 获取集合中所有文档对应值得最大值。
  • $push 在结果文档中插入值到一个数组中。
  • $addToSet 在结果文档中插入值到一个数组中,但不创建副本。
  • $first 根据资源文档的排序获取第一个文档数据。
  • $last 根据资源文档的排序获取最后一个文档数据

aggregate 对象方法

  • aggregate.group()
  • aggregate.sort()
  • aggregate.exec()
  • .....

2.9 联表

aggregate $lookup

teamsModel.aggregate([
    {$lookup: {from:'members', localField: 'name',foreignField:'team', as:'members'}},
    {$project: {name:1, introduction:1,power:1,'members.name':1,'members.nickName':1}},
    {$match:{name:'复仇者'}}
], (err, res) => {
    if (err) throw err;
    console.log(res[0]);
})
membersModel.aggregate([
    {$lookup: {from:'teams', localField:'team', foreignField:'name', as:'teams'}},
    {$project: {_id:0, nickName:1, teamName:'$team', teamIntroduction:'$teams.introduction'}}
], (err, res) => {
    console.log(res);
})
  • from 需要关联的集合名
  • localField 本集合中需要查找的字段
  • foreignField 另外一个集合中需要关联的字段
  • as 输出的字段名

populate

在一个Collection(articles)中定义一个指向另一个Collection(users)的_id字段的字段(by)

//定义Schema
const membersSchema = new mongoose.Schema({
    name: String,
    nickName: String,
    age: Number,
    team: {type:mongoose.Schema.Types.ObjectId, ref: 'teams'}
});
const teamsSchema = new mongoose.Schema({
    name: String,
    introduction: String,
    power: Number
});

//查询使用
membersModel
    .find({})
    .populate('team', 'name -_id')
    .exec((err, res) => {
        if (err) throw err;
        console.log(res);
    })

2.10 验证(Validate)

验证器规则

  • 验证是在SchemaType上定义的。
  • 验证是中间件。Mongoose 验证器作为pre('save')前置钩子在每个模式默认情况下执行。
  • 你可以手动使用document运行验证。validate(callback) 或 doc.validateSync()。
  • 验证程序不运行在未定义的值上,除了required验证器。
  • 验证异步递归;当你调用Model#save,子文档验证也可以执行。如果出现错误,你的 Model#save回调接收它。
  • 验证是可自定义的。

内置验证器

  • 所有的SchemaType都有内置的required验证器。所需的验证器使用SchemaTypecheckrequired()函数确定值是否满足所需的验证器。
  • 数值( Numbers )有最大(man)和最小(min)的验证器。
  • 字符串(String)有enummatchmaxLengthminLength验证器。
const UsersSchema = new mongoose.Schema({   
  name: {   
    type: String,   
    unique: true,
    required: true,   
    minlength: 3,   
    maxlength: 6   
  },   
  age: {   
    type: Number,   
    min: [18, '未成年人禁止入内'],   
    max: 30,   
    required: true   
  },   
  sex: {   
    type: String,   
    enum: {   
      values: ['male', 'female'],   
      message: '`{PATH}` 是 `{VALUE}`, 您必须确认您的性别!'   
    },   
    required: true   
  }  
});   

{PATH}{VALUE} 是错误提示中的变量

{PATH}: 键名

{VALUE}: 当前键值

{TYPE}:验证器类型,比如min,regexp等

{MIN}: 最小值,只存在数值

{MAX}:最大值,只存在数值

自定义验证器

const UsersSchema = new Schema({   
  ...   
  phone: {   
    type: String,   
    validate: {   
        validator: function(v){
            return /1[3|5|7|8]\d{9}/.test(v);       
        },   
        message: '`{PATH}` 必须是有效的11位手机号码!'   
    },   
    required: true   
  }  
});  

也可以使用数组添加多个验证器

validate: [  
  {   
    validator: fn,   
    message: ''   
  },
  {   
    validator: fn,   
    message: ''   
  }
]

进行验证

  • 进行save()操作的时候进行验证,回调函数第一个参数

  • document.validate() 进行验证 回调得到错误对象

  • document.validateSync() 同步验证,返回err对象

  • 验证失败,返回ValidatorError对象, 改对象的errors 属性,下包含每个字段的错误。每个字段的错误都包含一下属性

    kind 验证器类型,比如min,regexp等

    path 键名(属性名)

    value 值

    message 错误信息

  • 默认情况下,验证器都是只有save操作才会触发,但是在 Mongoose 4.x后,我们也可以开启update()和findoneandupdate()的验证器,只需将runValidators设为true(默认是false):

    const opts = { runValidators: true };   
    UsersModel.update({}, { name: 'Superman' }, opts, (err) => { });
    

2.11 中间件

中间件(也称为前置和后置钩子)是异步函数执行过程中传递的控制的函数。中间件是在schema级别上指定的

① 中间件类型

document 中间件,

支持下列操作( this 指向当前 document)

  • document.validate()
  • document.save()
  • document.remove()
query 中间件

支持下列操作 (this 指向当前 query)

  • Model.count()
  • Model.find()
  • Model.findOne()
  • Model.findOneAndRemove()
  • Model.findOneAndUpdate()
  • Model.update()
aggregate 中间件

作用于 MyModel.aggregate() 它会在你对 aggregate 对象调用 exec() 时执行。 对于 aggregate 中间件,this 指向当前aggregation 对象

model 中间件,

this 指向当前 model。 Model 中间件支持以下 Model 操作:

  • Model.insertMany()

② 前置钩子 (pre)

串行

串行中间件一个接一个地执行。具体来说, 上一个中间件调用 next 函数的时候,下一个执行。

var schema = new Schema(..);
schema.pre('save', function(next) {
  // do stuff
  next();
});
并行

并行中间件提供细粒度流控制。

var schema = new Schema(..);

// `true` means this is a parallel middleware. You **must** specify `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);
});

在这个例子里,save 方法将在所有中间件都调用了 done 的时候才会执行。

使用场景
  • 复杂的数据校验
  • 删除依赖文档(删除用户后删除他的所有文章)

③ 后置钩子 (post)

post 中间件在方法执行之后 调用,这个时候每个 pre 中间件都已经完成

schema.post('validate', function(doc) {
  console.log('%s has been validated (but not saved yet)', doc._id);
});
schema.post('save', function(doc) {
  console.log('%s has been saved', doc._id);
});
schema.post('remove', function(doc) {
  console.log('%s has been removed', doc._id);
});

如果你给回调函数传入两个参数,mongoose 会认为第二个参数是 next() 函数,你可以通过 next 触发下一个中间件

2.12 插件

语法

ArticlesSchema.plugin(function(schema, options){
    schema.add({lastMod: Date});
    schema.pre('save', function(next){
        this.lastMod = Date.now();
        next();
    })
}, [option])

全局插件

mongoose单独有一个plugin()功能为每一个schema注册插件:

mongoose.plugin(pluginsFn);

第三方插件

http://plugins.mongoosejs.io/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容