MongoDB(二)mongoose

MongoDB(一)_qfc_128220的博客-优快云博客

上一节介绍了MongoDB的基础用法,我们已经可以在Mongo Shell或者其他可视化数据库软件上通过手动调用命令或方法进行MongoDB数据库操作了。

目录

MongoDB数据库驱动

使用MongoDB数据库驱动

连接数据库服务器

创建或选择数据库

创建或选择集合

文档CRUD操作

向集合中插入文档

查询集合中的文档

更新集合中的文档

删除集合中的文档

MongoDB数据库的问题

mongoose

mongoose介绍

mongoose连接MongoDB数据库

创建约束规则:Schema

type

required

default

enum

min,max 

minLength,maxLength

trim

validate

创建带有约束的集合:Model

Model构造函数

Model构造函数的作用

Model构造函数上的实例方法

Model.prototype.save()

Model构造函数上的静态方法

Model.create(docs, options, callback)

 Model.find(query, projection, options, callback)

Model.findById(objectId, projection, options, callback)

Model.findOne(query, projection, options, callback) 

find,findOne,findById查询结果的使用

Model.updateOne(query, doc, options, callback)

Model.updateMany(query, doc, options, callback)

Model.deleteOne(query, options, callback)

Model.deleteMany(query, options, callback)


MongoDB数据库驱动

但是,在真实系统中,后端服务器需要完成和数据库服务器的数据交互,即后端服务器需要通过后端程序语言来完成数据库的操作,而不是通过人工手动在Mongo Shell或者其他可视化数据库软件上完成。

此时,必然涉及到将后端服务器语言转化为MongoDB数据库能识别的数据库操作的“翻译行为”,而这个翻译行为就是由“数据库驱动”完成的。

而后端服务器开发语言很多,目前MongoDB提供了如下后端语言的驱动

Start Developing with MongoDB — MongoDB Drivers

 针对Node.js也提供了对应的驱动。

使用MongoDB数据库驱动

连接数据库服务器

MongoDB针对Node.js提供的驱动,其实就是Node.js的一个第三方模块,我们可以通过npm来下载

npm i mongodb

下载好数据库驱动后,我们就可以在项目中引入该驱动

const { MongoClient } = require('mongodb')

在驱动中我们可以解析出一个MongoClient构造函数,该构造函数可以new出一个连接数据库服务器的客户端实例

const client = new MongoClient('mongodb://username:password@ip:port')

连接数据库服务器时,必然需要知道数据库服务器所在网路地址,这里的网络地址中:

mongodbMongoDB专用的通信协议
username:passwordMongoDB数据库服务器的接入用户名和密码
ip:portMongoDB数据库服务器所在的ip/域名和端口号

如果MongoDB数据库服务器没有设置用户名密码,则可以省略username:password

如果MongoDB数据库服务器的端口号是27017,则端口号可以省略,因为27017是MongoDB默认端口号 

由于数据库相关操作都是异步的,比如连接数据库,数据库集合CRUD文档等操作,所以MongoDB数据库驱动将相关数据库操作都进行了Promise封装,并且数据库集合CRUD文档的方法和MongoDB原生方法用法几乎一致,只需要多考虑一个异步的操作结果即可

创建或选择数据库

const { MongoClient } = require('mongodb')

const client = new MongoClient('mongodb://localhost')

client.connect().then(()=>{
    
    const db = client.db('my')  // 相当于 use my

}).catch(console.dir)

创建或选择集合

const { MongoClient } = require('mongodb')

const client = new MongoClient('mongodb://localhost')

client.connect().then(()=>{
    
    const db = client.db('my')

    const collection = db.collection('user') // 相当于 db.user

}).catch(console.dir)

文档CRUD操作

向集合中插入文档

插入操作:insertOne、insertMany (不推荐使用insert,已过时)

查询集合中的文档

查询操作:findOne,find

 使用findOne方法时,结果就是被查询的文档对象

 使用find时, 结果rst是一个FindCursor对象,我们无法直接基于该对象获取到想要的信息 

为什么findOne可以异步地获得被查询的文档对象,而findMany不能异步地获得被查询的多个文档对象呢?

因为“量”的问题,在MongoDB官方文档解释

Read operations that return multiple documents do not immediately return all values matching the query. Because a query can potentially match very large sets of documents, these operations rely upon an object called a cursor. A cursor fetches documents in batches to reduce both memory consumption and network bandwidth usage. Cursors are highly configurable and offer multiple interaction paradigms for different use cases.

简单翻译就是:findMany可能会查出大量文档,如果一下子返回的话,可能会把内存撑爆了,因为从数据库文件中查询出来的东西,实际就是文件IO,即将磁盘数据读取到内存中。为了防止findMany结果将内存撑爆,所以提供了一个游标对象FindCursor,通过该游标对象可以实现内存友好的结果集读取操作。

关于游标对象的工作模式

You can work with cursors using a number of cursor paradigms. Most cursor paradigms allow you to access query results one document at a time, abstracting away network and caching logic. However, since use cases differ, other paradigms offer different access patterns, like pulling all matching documents into a collection in process memory.

这里意思是:游标对象有两种工作模式,

一是:每次从结果集中读取一条文档到内存中,然后我们一条一条的处理,

二是:一次性将结果集中所有文档都加载到内存中,我们集中处理

很明显,第一种工作模式,内存友好,主要针对数据量大的情况,第二种工作模式,逻辑友好,主要针对数据量小的情况。

所以FindCursor对象实例方法常用的有两个forEach,toArray

更新集合中的文档

更新操作:updateOne、updateMany(update不推荐使用,已过时)

删除集合中的文档

删除操作:deleteOne、deleteMany (remove不推荐使用,已过时)

MongoDB数据库的问题

通过MongoDB驱动的增删改查文档的学习,我们发现其实基于驱动来来操作MongoDB,和通过Mongo shell来操作MongoDB,差别不是很大,唯一的差别就是基于驱动来操作MongoDB需要注意异步返回结果的问题。所以学会了MongoDB,再学习MongoDB驱动,没有多大的压力。

但是尴尬的是,如今主流的还是关系型数据库,因为关系型数据库中的表具有约束性,能根据表字段的类型,范围等约束数据,使被存储的数据更规整,有利于后期使用与维护。

而非关系型数据库,如MongoDB没有表的概念,唯一含义相近的是“集合”,但是集合没有字段定义,也没有约束性,存入集合的文档,可以是任意格式的文档,这可能会造成集合中文档数据的不统一和难以维护。

对比,集合和表,集合唯一的优点也正是其缺点,就是集合中的文档不受集合的约束,所以文档的字段增加或减少不会影响其入集合,而表中数据由于受到表的约束,所以表结构一旦改变,就会对所有表数据造成影响,所谓牵一发而动全身是也。

那么有没有一种可能,在不改变MongoDB特性的前提下,对插入集合的文档进行“约束”呢?

目前,MongoDB数据库的驱动并没有实现这一功能,其实实现逻辑也很简单,即将“约束”这一被动行为,转为主动行为,即以前我们期望“集合”在插入文档时,被动触发“约束”验证,现在我们设想,在文档被插入集合前,就进行一次“约束“验证。

mongoose

mongoose介绍

mongoose是一个Node.js第三方模块,它是基于mongodb驱动二次开发的新驱动。(这里新驱动,是我自己理解的)

mongoose主要解决的问题就是:为MongoDB的集合加入”约束“能力

mongoose中将”约束“称为Schema(模式),而将”集合“概念取代为Model(模型),二者结合便产生了带有约束能力的集合。具体使用实现步骤如下

const mongoose = require('mongoose')

const Schema = mongoose.Schema

// 创建约束,类似于表字段定义
const userSchema = new Schema({
    name: {
        type: String,
        required: true
    },
    age: {
        type: Number,
        required: true,
        min: 0,
        max: 100
    }
})

// 创建模型,即创建带有约束的集合
const User = mongoose.model('user', userSchema)

// 向集合中插入文档
User.create({
    name: 'qfc',
    age: 18
})
.then((...args)=>{
    console.log(args)
})
.catch(console.dir)

创建约束,使用的是mongoose.Schema构造函数,约束即为mongoose.Schema的实例,构造函数参数即为约束规则

创建模型,使用的是mongoose.model方法,第一个参数为模型名,第二个参数为约束实例,返回的一个模型构造函数

向集合中插入文档,模型构造函数调用方法create,传入一个JS对象作为文档内容值,create方法是一个异步API,支持Promise

mongoose连接MongoDB数据库

mongoose连接MongoDB数据库基本继承了mongodb驱动的方式,即利用connect方法,该方法的结果是异步的,支持Promise。

和mongodb驱动连接数据库的唯一区别是,mongoose.connect可以直接指定要连接的数据名字dbname,即

mongoose.connect('mongodb://username:password@ip:port/dbname')

 通常情况下,连接数据库,完成相应数据库操作后,都需要关闭与数据库之间的连接,以期节省数据库服务器的资源,因为每一条连接,都需要数据库服务器分配一定的内存和CPU资源,当有太多的客户端与数据库建立连接后,就会造成数据库服务器的拒绝连接现象,所以为了避免这种情况,我们最好是使用完数据库后,就断开与数据库之间的连接。

但是如果数据库是专门用于后端服务器的,则此时理论上来讲,不会有太多的客户端与数据库建立连接,常备连接只有一个,此时我们一般不需要关闭后端服务器和数据库之间的连接,因为频繁的建立连接,断开连接,同样会浪费数据库服务器的资源,不如一直维持连接状态。

mongoose断开与数据库之间的连接使用disconnect方法

创建约束规则:Schema

在前面代码中,我们可以发现创建Schema实例时,传入的参数,非常类似于关系型数据库表字段定义,这里我们将传入的参数称为字段的约束规则,而mongoose提供了很多的约束规则,常用的有如下

约束规则名约束规则含义
type字段类型,支持几乎所有JS类型
requiredtrue 字段必传,false字段可选
default字段默认值
enum字段枚举值,类型为数组
min字段数值最小值
max字段数值最大值
minLength字段字符串最小长度
maxLength字段字符串最大长度
trimtrue 去除字段字符串两端空格, false不去除
validate字段自定义验证器

type

type是最基本的约束规则,常用的有String,Number,Date,Boolean等,当字段只有type约束规则时,则可以直接将type值作为字段值

如果字段值不符合type指定的类型的话,则会报错

 这里只报错了age字段值不符合要求,看报错提示是说无法将"qfc"强转为Number类型。也就是说,mongoose会尽力自动帮我将字段现有值转换为指定type类型的值,这也是name字段值验证通过的原因,因为数值18可以被转为String类型。

此时虽然name,age字段值都不符合约束规则type的要求,但是可以被强转为对应类型,所以没有报错。

required

required 表示字段是否必传

default

default 用于设置默认值,即当该字段不传值时的默认值

enum

enum用于设置字段的枚举值,如果字段值不在枚举值范围内,则报错

min,max 

min,max 用于针对数值类型的字段,设置其数值范围

minLength,maxLength

minLength,maxLength用于针对字符串类型字段,限制其字段值长度

trim

trim 用于去除字符串两端空格

 

validate

 validate 用于自定义验证规则,如果上述约束规则还不能满足需求,则可以通过validate设置自定义验证规则,validate值是一个函数,函数参数自动接收字段值,我们在函数中对字段值进行验证,返回true表示验证通过,反之验证失败

创建带有约束的集合:Model

mongoose.model方法就是用于创建一个带有约束的集合:模型

mongoose.model(name, [schema], [collection]) 该方法可以传入三个参数

name

model name or class extending Model

模型名

schema

the schema to use

Schema实例

collection

name (optional, inferred from model name)

集合名,不指定的话,可以从模型名推断出来

这里需要注意的是:mongoose.model返回一个模型Model,模型Model等价于带有约束的集合。

我们可以这样理解:name模型 = schema约束 + collection集合

如果不指定collection集合名,则mongoose可以根据model名推断出collection名。

mongoose.model('user', userSchema) 对应的集合名就是 users,如果不指定collection集合名,则mongoose会自动将模型名变为复数,作为集合名。

 还挺智能,child还能变children

如果指定了collection名字,则操作指定名字的collection

另外需要注意的是,和MongoDB创建集合不同,mongoose.model创建集合会直接持久化到磁盘中,类似于db.createCollection('test')

mongoose.model方法返回一个Model构造函数

Model构造函数

Model构造函数的作用

Model构造函数的作用有两个:

1、创建文档实例,基于文档,进行数据库操作

2、通过构造函数的静态方法,基于集合,进行类似于db.<collection>.CRUD的文档操作

其实,从面向对象的角度来思考,Model构造函数 其实就是 文档对象的类

mongoose.model返回的Model构造函数,其实包含了约束字段的定义,而这些约束字段其实就是文档对象的字段,从Model其实除了等价于 “约束 + 集合”,还是文档对象的类。

我们可以根据Model构造出文档对象。

但是创建出文档实例,并不能自动执行插入集合的操作。

创建文档实例时,mongoose自动为我们生成_id字段即值。

Model构造函数上的实例方法

Model构造函数原型上存在很多实例方法,下面是官方文档中关于Model.prototype的介绍

Mongoose v6.2.1: (mongoosejs.com)

 

文档的操作主要涉及的就是:

增(save)删(remove,deleteOne)改(对象操作)查 (没有查操作)

Model.prototype.save()

save方法是一个异步方法,返回一个Promise对象,该对象包含插入结果。

为什么没有为Model实例提供查询方法,其实很简单,我们查询文档是为了获得文档,但是现在文档对象就是我们自己通过Model实例化出来的,所以不需要查询。

同样的,文档的删除和文档的更新,都依赖于于查询,我们期望的是删除或更新从集合中查询出来的文档,而不是我们自己构造的文档。所以这里暂不介绍,等后面有查询出来的文档对象时再介绍。

Model构造函数上的静态方法

Model.create()

Model.findOne()

Model.find()

Model.update() 过时

Model.updateOne()

Model.updateMany()

Model.remove() 过时

Model.deleteOne()

Model.deleteMany()

Model.create(docs, options, callback)

插入单个或多个文档到集合,返回一个Promise对象,结果为被插入的文档对象

 Model.find(query, projection, options, callback)

查询一个或多个文档,返回一个Promise对象,结果为被查询到的,文档对象

注意,这里Model.find并没有像mongodb驱动一样搞一个FindCursor游标对象来防止一次性查出大量数据撑爆内存,所以需要注意过滤条件的设置,或者通过limit限制查询数量。

其他的用法和MongoDB原生find方法用法一致,比如投影字段的设置,查询配置的设置。

Model.findById(objectId, projection, options, callback)

根据_id查询文档,返回一个Promise对象,结果是被查询的文档

这里mongoose只需要我们传入文档对应的_id的内容值,然后mongoose自动进行ObjectId包装

Model.findOne(query, projection, options, callback) 

用法和find一致,区别是只会查询出第一个匹配的文档

find,findOne,findById查询结果的使用

上面三个Model构造函数上的静态查询方法,返回的Promise结果都是Model的实例对象,即文档对象,所以可以使用Model.prototype上定义的实例方法,比如更新和删除

Model.prototype没有提供更新操作的方法,但是可以直接将doc看出普通对象,进行对象属性的操作,或者使用get,set方法操作,最后调用save方法进行覆盖,也能达到更新的效果

 

 Model.prototype.deleteOne(),Model.prototype.remove()

Model.updateOne(query, doc, options, callback)

返回一个Promise对象,结果是更新结果

 updateOne只能更新匹配到的第一个文档

Model.updateMany(query, doc, options, callback)

返回一个Promise对象,结果为更新结果

updateMany可以更新匹配到的所有文档

Model.deleteOne(query, options, callback)

返回一个Promise对象,结果为删除结果

deleteOne只会删除匹配到的第一个文档

Model.deleteMany(query, options, callback)

 返回一个Promise对象,结果为删除结果

deleteMany可以删除匹配到的所有文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员阿甘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值