Mongoose增查改删学习笔记

初学Node.js接触到MongoDB数据库,阅读资料中推荐的都是Mongoose模块,可以更加方便的对数据库进行操作,便开始接触Mongoose。在学习时碰到许多基础问题,查阅了许多资料理来理解,此篇文章就是以自己的理解,记录下入门的基础知识,以及归纳下常用的函数以及格式,方便今后查阅。
初学笔记难免有许多不足与错误欢迎指出。

环境搭建

安装MongoDB

如果需要在本地测试,需要先安装MongoDB数据库。
官网下载最新安装包。安装成功后,在命令行下进入MongoDB\bin目录

mongod --dbpath D:\MongoDB\data

启动数据库。

http://localhost:27017/

在浏览器中访问这地址,访问正常则数据库启动成功。
更详细的说明可以查看《MongoDB的下载、安装与部署》

Node.js中安装模块

npm install mongoose

在已经安装过Node.js的电脑上,进入工作目录输入以上命令即可安装Mongoose模块。

可视化工具

Robomongo

Robomongo是一款不错的可视化工具,Windows、MacOS、Linux平台都有,界面也简洁。虽然有收费,普通操作免费版足够用。
官网下载

引入与连接

引入模块

在需要使用的js文件中引入模块。

var mongoose = require('mongoose');

连接数据库

var db = mongoose.connect('mongodb://localhost/mongodb');

URL以mongodb:// + [用户名:密码@] +数据库地址[:端口] + 数据库名。(默认端口27017)
需要对连接状况进行判断,可以用以下代码:

db.connection.on("error", function (error) {  
    console.log("数据库连接失败:" + error);
});

db.connection.on("open", function () {  
    console.log("数据库连接成功"); 
})

db.connection.on('disconnected', function () {    
    console.log('数据库连接断开');  
})

基本概念

最常接触到的有三个概念SchemaModelEntity。按自己理解,
Schema是定义数据库的结构。类似创建表时的数据定义,但比创建数据库可以做更多的定义,只是没办法通过Schema对数据库进行更改。
Model是将Schema定义的结构赋予表名。但可用此名对数据库进行增删查改。
Entity是将Model与具体的数据绑定,可以对具体数据自身进行操作,例如保存数据。

Schema

Schema用来定义数据库文档结构,数据库有什么字段、字段是什么类型、默认值、主键之类的信息。除了定义结构外,还能定义文档的实例方法,静态模型方法,复合索引,中间件等。详情查看mongoose官方文档。
在引入Mongoose模块var mongoose = require('mongoose')的js文件中进行操作。

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

这样即定义了一个名为blogSchema的Schema。

如需再添加数据,用add方法。

blogSchema.add( { author: String, body: String} );

资料中介绍,Shema不仅定义了文档的结构和属性,还可以定义文档的插件、实例方法、静态方法、复合索引文档生命周期钩子,具体还需查看官方文档。

Schema.Type

Schema.Type是Mongoose内部定义的数据类型。基本类型有:StringNumberDateBooleanArrayBufferMixedObjectId

Mixed
混合数据类型,可以直接定义{}来使用,以下两种形式等价。

new Schema({mixed: {Schema.Types.Mixed} });
new Schema({mixed: {} });

ObjectId
储存在数据库中的每个数据都会有默认的主键_id,默认存储的是ObjectId
ObjectId是一个12字节的BSON类型字符串。按照字节顺序依次代表:
4字节:UNIX时间戳
3字节:表示运行MongoDB的机器
2字节:表示生成此_id的进程
3字节:由一个随机数开始的计数器生成的值

Model

var blogModel = mongoose.model('Blog', blogSchema);

将名为blogSchema的Schema与Blog名字绑定,即是存入数据库的名字,但存入数据库中的名字是Blogs,会自动添加一个s
这里将Model命名为blogModel,需要对Blog表操作的话,都需要使用变量名blogModel

Entity

可以绑定具体数据对Model实例化。

var blogEntity = new blogModel({
    title:  "Mongoose",
    author: "L",
    body:   "Documents are instances of out model. Creating them and saving to the database is easy",
    comments: [{ body: "It's very cool! Thanks a lot!", date: "2014.07.28" }],
    hidden: false,
    meta: {
        votes: 100,
        favs:  99
    }
})

这里将名为blogModel的Model实例化。之后我们可以用blogEntity名对数据进行保存并执行回调。

blogEntity.save(function(err, docs){
    if(err) console.log(err);
    console.log('保存成功:' + docs);
})

在平常使用SQL语句操作数据库时,取得数据后先组织成SQL语句,然后放入执行语句中执行。这里理解也是类似,取得数据先进行实例化,这一步类似于组织成SQL语句,然后再做具体操作例如上面的Save操作。但由于Node.js是异步操作,所以返回的数据利用回调函数来进行操作。

知道了以上概念后就可以对数据进行操作了,下面将列出一些常用的资料,并附上相应的例子。

增查改删(CRUD)

所有的参数都是以JSON对象形式传入。

增(C)

Model.create(doc(s), [callback])

var doc = ({
    title:  "Mongoose",
    author: "L",
    body:   "Documents are instances of out model. Creating them and saving to the database is easy",
    comments: [{ body: "It's very cool! Thanks a lot!", date: "2014.07.28" }],
    hidden: false,
    meta: {
        votes: 100,
        favs:  99
    }
};

blogModel.create(doc, function(err, docs){
    if(err) console.log(err);
    console.log('保存成功:' + docs);
});

Model#save([options], [options.safe], [options.validateBeforeSave], [fn])

var blogEntity = new blogModel({
    title:  "Mongoose",
    author: "L",
    body:   "Documents are instances of out model. Creating them and saving to the database is easy",
    comments: [{ body: "It's very cool! Thanks a lot!", date: "2014.07.28" }],
    hidden: false,
    meta: {
        votes: 100,
        favs:  99
    }
});

blogEntity.save(function(err, docs){
    if(err) console.log(err);
    console.log('保存成功:' + docs);
});

Model.insertMany(doc(s), [options], [callback])

多条数据插入,将多条数据一次性插入,相对于循环使用create保存会更加快。

blogModel.insertMany([
    {title: "mongoose1", author: "L"}, 
    {title: "mongoose2", author: "L"}
    ], function(err, docs){
        if(err) console.log(err);
        console.log('保存成功:' + docs);
});

查(R)

Model.find(conditions, [projection], [options], [callback])

conditions:查询条件;projection:控制返回的字段;options:控制选项;callback:回调函数。

blogModel.find({title: "Mongoose", meta.votes: 100}, {title: 1, author: 1, body: 1}, function(err, docs){
    if(err) console.log(err);
    console.log('查询结果:' + docs);
})

查询“title”标题为“Mongoose”,并且“meta”中“votes”字段值为“100”的记录,返回仅返回“title”、“author”、“body”三个字段的数据。

Model.findOne([conditions], [projection], [options], [callback])

conditions:查询条件;projection:控制返回的字段;options:控制选项;callback:回调函数。
只返回第一个查询记录。

Model.findById(id, [projection], [options], [callback])

id:指定_id的值;projection:控制返回的字段;options:控制选项;callback:回调函数。

改(U)

Model.update(conditions, doc, [options], [callback])

conditions:查询条件;doc:需要修改的数据,不能修改主键(_id);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模式,允许覆盖记录。

blogModel.update({title: "Mongoose"}, {author: "L"}, {multi: true}, function(err, docs){
    if(err) console.log(err);
    console.log('更改成功:' + docs);
})

以上代码先查询“title”为“Mongoose”的数据,然后将它的“author”修改为“L”,“multi”为true允许更新多条查询记录。

Model.updateMany(conditions, doc, [options], [callback])

一次更新多条

Model.updateOne(conditions, doc, [options], [callback])

一次更新一条

Model.findByIdAndUpdate(id, [update], [options], [callback])

id:指定_id的值;update:需要修改的数据;options控制选项;callback回调函数。
options有以下选项:
  new: bool - 默认为false。返回修改后的数据。
  upsert: bool - 默认为false。如果不存在则创建记录。
  runValidators: 如果值为true,执行Validation验证。
  setDefaultsOnInsert: 如果upsert选项为true,在新建时插入文档定义的默认值。
  sort: 如果有多个查询条件,按顺序进行查询更新。
  select: 设置数据的返回。

Model.findOneAndUpdate([conditions], [update], [options], [callback])

conditions:查询条件;update:需要修改的数据;options控制选项;callback回调函数。
options有以下选项:
  new: bool - 默认为false。返回修改后的数据。
  upsert: bool - 默认为false。如果不存在则创建记录。
  fields: {Object|String} - 选择字段。类似.select(fields).findOneAndUpdate()。
  maxTimeMS: 查询用时上限。
  sort: 如果有多个查询条件,按顺序进行查询更新。
  runValidators: 如果值为true,执行Validation验证。
  setDefaultsOnInsert: 如果upsert选项为true,在新建时插入文档定义的默认值。
  passRawResult: 如果为真,将原始结果作为回调函数第三个参数。

删(D)

Model.remove(conditions, [callback])

blogModel.remove({author: "L"}, function(err, docs){
    if(err) console.log(err);
    console.log('删除成功:' + docs);
})

删除“author”值为“L”的记录。

Model.findByIdAndRemove(id, [options], [callback])

id:指定_id的值;update:需要修改的数据;options控制选项;callback回调函数。
options有以下选项:
  sort: 如果有多个查询条件,按顺序进行查询更新。
  select: 设置数据的返回。

Model.findOneAndRemove(conditions, [options], [callback])

conditions:查询条件;update:需要修改的数据;options控制选项;callback回调函数。
options有以下选项:
  sort: 如果有多个查询条件,按顺序进行查询更新。
  maxTimeMS: 查询用时上限。
  select: 设置数据的返回。

复杂条件查询

在之前的查询说明中仅演示了确定值的查询,如果遇到更加复杂的情况就需要使用其他一些方法。
详细的文档可以在这儿查找 mongodb查询符

Query#exec([operation], [callback])

执行查询,回调函数。
使用find()$where之类查询返回的是Mongoose自己封装的Query对象,使用find()可以在函数最后接上回调来获取查询到的数据。
使用链式语句时,可以在之后接.exec()执行查询,并指定回调函数。

blogModel.find({title: "Mongoose", meta.votes: 100}, {title: 1, author: 1, body: 1}).exec(function(err, docs){
    if(err) console.log(err);
    console.log('查询结果:' + docs);
})

配合各种查询符可以方便的实现复杂的查询。
比如我需要查询“title”中以“Mongoose”开头,并且“meta”中“votes”的值小余100。并且按“meta”中“votes”的值升序排序。

blogModel.and([
    { title: { $regex: "Mongoose.+","$options":"i"}},
    { meta.votes: { $lt: 100}}
).sort({ meta.votes: 1}
).exec(function(err, docs){
    if(err) console.log(err);
    console.log('查询结果:' + docs);
});

比较查询运算符

$equals 等于 / $gt 大于 / $gte 大于等于 / $lt 小余 / $lte 小余等于 / $ne 不等于 / $in 在数组中 / $nin 不在数组中

blogModel.find({meta.votes: {$lt: 100}});

查询“meta”中的“votes”字段值小余100的数据。

blogModel.find({title: {$in: ['Mongoose', 'Mongodb', 'Nodejs']}});

查询“title”为“Mongoose”或“Mongodb”或“Nodejs”其中之一的数据。

逻辑查询运算符

$or 或 / $and 与 / $nor

blogModel.find({ $and: [
    {meta.votes: {$gte: 50}}, 
    {meta.votes: {$lt: 100}}
]});

查询“meta”中的“votes”字段值大于等于50到小余100的数据。

blogModel.find({ $nor: [
    {meta.votes: 50}, 
    {meta.votes: 100}
]});

查询“meta”中的“votes”字段值不等于50和不等于100的数据。

以上例子也可以写成这样形式,比较清晰,其他类同

blogModel.and([
    {meta.votes: {$gte: 50}}, 
    {meta.votes: {$lt: 100}}
]);
    
blogModel.nor([
    {meta.votes: 50}, 
    {meta.votes: 100}
]);

元素查询运算符

$exists 查询的字段值是否存在

blogModel.find({ title: {$exists: true}});
blogModel.where('title').exists(true);

查询存在“title”字段的数据。

评估查询运算符

$mod 与数据进行取模运算筛选

blogModel.find({ meta.votes: {$mod: [4, 0]}});
blogModel.where('meta.votes').$mod(4, 0);

查找“meta”中的“votes”字段值与4取模后,值为0的数据。

$regex 使用正则表达式查询数据

blogModel.find({ title: { $regex: "Mongoose.+","$options":"i"}});

搜索以“Mongoose”开头的“title”字段,“options”中的“i”代表不区分大小写。
$options参数与其余用法可以查看mongodb文档中 $regex 一节。

$where 支持js表达式查询

blogModel.find({ $where: 'this.comments.length === 10 || this.name.length === 5' });
blogModel.$where(function() { return this.comments.length === 10 || this.name.length === 5; });

数组查询运算符

Query#all([path], val)  查询数组的本身及超集

blogModel.find( tags: ['nodejs', 'mongoose']);

查询“tags”的字段值同时包含有['nodejs', 'mongoose']的数据。只要值中包含此数组即返回数据,若是只包含数组中的一个则不返回此数据。

Query#elemMatch(path, criteria)  查询数组的交集

blogModel.find( $elemMatch: { tags: 'mongoose', author: 'L'});

查询“tags”为“mongoose”或是“author”为“L”的数据。

Query#size([path], val)  查询指定大小的数组

blogModel.find( tags: { $size: 2});
blogModel.where('tags').size(2);

查询“tags”数组中包含两个元素的数据。

其他常用的运算符

Query#limit(val)  限制查询返回的数量

blogModel.find( tags: 'mongoose').limit(5);

查询“tags”为“mongoose”的数据,只返回前5个查询结果。

Query#skip(val)  跳过前N个查询结果

blogModel.find( tags: 'mongoose').skip(10).limit(5);

查询“tags”为“mongoose”的数据,跳过前10个查询结果,返回从第11个开始的五个查询结果。
做分页时常用到这两个,但数据量过大时就会有性能问题。

Query#sort(arg)  对结果按某个指定字段进行排序

1asc为升序,-1desc为降序。可以对一个字段进行排序,也可以是多个。

blogModel.find( tags: 'mongoose').skip(10).limit(5).sort("{ meta.votes: 1}");

查询“tags”为“mongoose”的数据,跳过前10个查询结果,返回从第11个开始的五个查询结果。之后按“votes”进行升序排序。

Query#count([criteria], [callback])  计数

blogModel.count({ title: 'mongoose'}, function(err, docs){});

统计“title”为“mongoose”数据的数量

Query#select(arg)  选择指定字段

在查询中可以选择指定的查询字段,或者排除指定的字段。+为包含,-为排除。

blogModel.select('title body');

只包含“title”、“body”字段。

blogModel.select('-title -body');

排除“title”、“body”字段。


01小径
在路上,遇见了一只BUG,我将它抓住,收藏在了这里。<( ̄︶ ̄)>
651 声望
14 粉丝
0 条评论
推荐阅读
Theia 开发环境搭建
Node.js >= 16.14.0 and < 17.If you are interested in Theia's VS Code Extension support then you should use a Node version at least compatible with the one included in the version of Electron ...

LnEoi1阅读 180

「彻底弄懂」this全面解析
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在 哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在 函数执行的过程中用到...

wuwhs17阅读 2.1k

封面图
反编译微信小程序获取小程序前端源码wxapkg
研究反编译的原因就是我自己辛苦了半个月写的小程序,忘记备份放在桌面,心急体验Win11系统 重装系统忘记备份源码,后悔莫及。 后来网上找了反编译的教程,反编译已经上线了的小程序 于是自己尝试了一下,真的可...

TANKING12阅读 9.3k评论 7

封面图
万字详解,吃透 MongoDB!
MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统,由 C++ 编写的。MongoDB 提供了 面向文档 的存储方式,操作起来比较简单和容易,支持“无模式”的数据建模,可以存储比较复杂的数据类型,是一款非常...

JavaGuide8阅读 1.8k

封面图
Nodejs 手动搭建 Express 项目
默认ejs模板只支持渲染以ejs为扩展名的文件,可能在使用的时候会觉得它的代码书写方式很不爽还是想用html的形式去书写。在这里可以使用engine 注册模板引擎的函数,让他处理指定后缀名的文件

yeungx6阅读 9k评论 5

PDF 预览和下载你是怎么实现的?
在开发过程中要求对 PDF 类型的发票提供 预览 和 下载 功能,PDF 类型文件的来源又包括 H5 移动端 和 PC 端,而针对这两个不同端的处理会有些许不同,下文会有所提及。

熊的猫7阅读 1.7k评论 1

封面图
HTTP,WebSocket 和 聊天室
在涉及到网络层面的相关内容时,不免会联系到 HTTP、TCP、WebSocket 等,但相信大部分人都并不是很清楚其中的一些关系和概念,特别是需要你去做语言表述时,网上有不少优秀的资料和文章,但知识仍需要自己去消化...

熊的猫8阅读 1.6k

封面图
651 声望
14 粉丝
宣传栏