Mongoose 基本操作

前提

  • 安装Node
  • 新建一个单独的目录安装MongoDB并开启服务
  • 新建项目目录,npm init初始化并安装mongoose包npm i mongoose -S
  • 建议npm i -g supervisor安装supervisor,用于监听当前目录下 node 和 js 后缀的文件,当这些文件发生改动时,supervisor 会自动重启程序。
  • 运行mongo连接数据库

核心

名词解释

  • Schema:以文件形式存储的数据库模型骨架,不具备操作数据库的能力
  • Model:由Schema发布生成的模型,具有抽象属性和行为的数据库操作对
  • Entity:由Model创建的实体,也可以对数据库进行操作

三者关系
Schema 生成 Model,Model 创造 Entity,Model 和 Entity 都可以对数据库进行操作,但 Model 比 Entity 更具操作性


应用

首先要知道,数据库的所有操作都是异步的。

基本操作
  • 增:save()
  • 删:remove()
  • 查:find(),findOne()
  • 改:update(),updateMany()

结合代码使用

# test.js
const mongoose = require('mongoose')
mongoose.Promise = global.Promise// 最好加上这句话
const Schema = mongoose.Schema
const log = console.log
const err = console.error

// 创建连接,因为数据库操作是异步的,使用then方法可保证连接之后才执行操作
const db = mongoose.connect('mongodb://localhost/test').then(() => {
    // Schema和Model变量名大写,entity变量名小写,集合名小写,如cats
    const CatSchema = Schema({ name: String }) // Schema

    const CatModel = mongoose.model('cats', CatSchema)  //Model

    const catEntity = new CatModel({ name: 'Dolb' })  //实体 entity
      
    //以下所有逻辑都在这里进行

})
  • 增加一条数据
    // 增
    catEntity.save((e,res)=>{
        if(e){
            err(e)
        }else{
            log('save suc')
        }
    })

这里我运行node test.js保存成功后退出程序并再次node test.js

运行两次node test.js

得到的是几条数据呢?

查看数据条目

得到了两条数据,结论是数据库的数据没有覆盖一说,哪怕内容相同,重复save只会重复添加。

为了方便后面的演示,将const catEntity = new CatModel({ name: 'Dolb' })中的name值分别改为MiaoMimi,这样数据库下的cats集合里就有以下四条数据:

当前数据

另外补充一下,使用Model来新增数据使用create方法,如:CatModel.create({name: 'Dot'} , callback)

  • 删除一条数据
    因为数据库的操作都是异步的,那么在保存和删除同时存在时我们不能保证代码的执行顺序,所以得到的很可能是我们不愿意看到的结果,假设代码是这个样子的:
    // 增
    catEntity.save((e, res) => {
        if (e) {
            err(e)
        } else {
            log('save suc')
        }
    })

    // 删除新增的Mimi的数据
    catEntity.remove({ name: 'Mimi' }, (e, res) => {
        if (e) {
            err(e)
        } else {
            log('remove Mimi suc')
        }
    })

运行node
结果

是不是和想象的完全不一样?那么怎样做到删除当前增加的条目呢?

    // 增
    catEntity.save((e, res) => {
        if (e) {
            err(e)
        } else {
            log('save suc')
            // 删除新增的Mimi的数据
            catEntity.remove({ name: 'Mimi' }, (e, res) => {
                if (e) {
                    err(e)
                } else {
                    log('remove Mimi suc')
                }
            })
        }
    })

将删除的代码放进增加的代码中,就可以保证删除在新增之后执行

运行node
数据

可以看到仍是5条数据,说明新增的Mimi被删除了,事实上remove的第一个参数不写就可以做到删除当前条目了:

    // 增
    catEntity.save((e, res) => {
        if (e) {
            err(e)
        } else {
            log('save suc')
            // 删除新增的Mimi的数据
            catEntity.remove((e, res) => {
                if (e) {
                    err(e)
                } else {
                    log('remove Mimi suc')
                }
            })
        }
    })

数据

可是假设现在我们需要删除所有name为Dolb的数据,又该怎么做呢?这个时候就要用到Model了⬇️

  • 删除所有符合条件的数据
    首先我们将前面的代码注释掉,再添加以下代码
    // 删除所有符合name值为Dolb的数据
    CatModel.remove({ name: 'Dolb' }, (e, res) => {
        if (e) {
            err(e)
        } else {
            log('remove Dolb suc')
        }
    })
数据
  • 删除所有数据
    删除所有数据即去掉remove方法的第一个参数
    CatModel.remove((e, res) => {
        if (e) {
            err(e)
        } else {
            log('remove Dolb suc')
        }
    })

这里为了接下来的操作就不执行这段代码了

  • 查找所有符合条件的数据
    CatModel.find({ name: 'Mimi' }, (e, res) => {
        if (e) {
            err(e)
        } else {
            log('find Mimi suc: There have ' + res)
        }
    })
  • 查找所有数据
    CatModel.find((e, res) => {
        if (e) {
            err(e)
        } else {
            log('find all cats suc: There have ' + res)
        }
    })
数据

到这里我误删了所有数据,所以重新新建的数据如下:

现在的数据
  • 修改
    CatModel.update({ name: 'Miao' }, { name: 'Miao2' }, (e, res) => {
        if (e) {
            err(e)
        } else {
            log('更新成功')
            // 只更新一个
            CatModel.updateOne({ name: 'Miao2' }, { name: 'Miao3' }, (e, res) => {
                if (e) {
                    err(e)
                } else {
                    log('更新第一个Miao成功')
                }
            })
        }
    })

    CatModel.updateMany({ name: 'Mimi' }, { name: 'Mimi2' }, (e, res) => {
        if (err) {
            err(err)
        } else {
            log('更新成功')
        }
    })
加入updateOne前后,数据的变化

所以update和updateMany有什么区别呢?


进阶
  • Middleware中间件,重点:next(),hook钩子。
  • Plugin插件,用来更新Schema的功能,而全局Plugin用于更新全部Schema的功能,所以全局plugin最好放在靠前的位置。
  • Validation验证器,验证存入数据库的数据
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const log = console.log
const err = console.error

// Plugin插件,用来更新Schema的功能
// 全局Plugin,更新全部Schema的功能,全局plugin最好放在靠前的位置
// 以下代码为所有操作打上时间戳
function TimePlugin(schema, options) {
    const str = 'save time'
    schema.pre('save', next => {
        console.time(str)
        next()
    })
    schema.post('save', next => {
        console.timeEnd(str)
    })
}
mongoose.plugin(TimePlugin)

const PostSchema = new Schema({
    title: {
        type: String,
        // Validation  验证器 验证存入数据库的数据
        // 验证器,validate是放在Schema下的字段用来做字段限制的
        validate: {
            validator: v => v.length < 10,
            message: '文章标题长度必须小于10'
        }
    },
    author: {
        type: Schema.Types.ObjectId,
        ref: 'User'
    },
    rawContent: String,
    create_at: {
        type: Date,
        default: Date.now
    },
    update_at: {
        type: Date,
        default: Date.now
    },
    comments: {
        type: Array
    },
    votes: Number,
    downs: Number
})

const UserSchema = new Schema({
    name: String,
    sex: Number,
    avatar: String,
    mail: String,
    password: String
})

const CommentSchema = new Schema({
    author: {
        type: Schema.Types.ObjectId,
        required: true
    },
    content: {
        type: 'string',
        required: true
    },
    postId: {
        type: Schema.Types.ObjectId,
        required: true
    }
})

const PostModel = mongoose.model('Post', PostSchema)
const UserModel = mongoose.model('User', UserSchema)
const CommentModel = mongoose.model('Comment', CommentSchema)

const db = mongoose.connect('mongodb://localhost/dolb').then(() => {

    // 1. 插入文章
    function addPost(post) {
        const postEntity = new PostModel(post)
        postEntity.save((e, result) => {
            if (e) {
                err(e)
            } else {
                log('保存成功啦')
                //    getPostByTitle('hello world')// 插入之后才能查到文章,而操作都是异步的,不能保证顺序,所以调用getPostByTitle放在addPost里能保证查得到
            }
        })
    }
    addPost({
        title: 'hello wo'
    })

    // 2. 根据id删除文章
    function removePostById(postId) {
        PostModel.remove({ _id: postId }, (e, res) => {
            if (e) {
                err(e)
            } else {
                log('删除所有id为...成功啦')
            }
        })
    }
    // removePostById('5a609fba3906973fc61f640b')

    // 3. 根据文章标题查找文章
    function getPostByTitle(title) {
        PostModel.find({ title }, (e, res) => {
            if (e) {
                err(e)
            } else {
                log(res)
            }
        })
    }

    // 4.更新文章
    function updatePost(id, title) {
        PostModel.update({ _id: id }, { title }, (e, res) => {
            if (e) {
                err(e)
            } else {
                log(res)
            }
        })
    }
    // updatePost('5a60a17d951bc340519c0e95','hey dolby')
})

// middleware中间件
PostSchema.pre('save', next => {
    log('hey i am pre hook')
    next()
})
PostSchema.post('save', next => {
    log('hey i am post hook')
})

执行现有的代码,得到:

hey i am pre hook
save time: 9.787ms
hey i am post hook
保存成功啦

证明钩子优先级大于自身回调


常见报错

  • *** is not a constructor
    解决办法:
    如:CatSchema is not a constructor。说明调用了new CatSchema来生成实例,但是Schema是不能生成实例的,必须到Model才能生成实例,修改new CatSchemanew CatModel

  • Schema is not defined
    解决办法:
    Schema需要引入,如const Schema = require('mongoose').Schema或者const Schema = mongoose.Schema


实践结论

  • 依赖于其他表时需要用到ObjectId
  • hook钩子(pre,next)优先级大于自身回调
  • populate用于处理关联表

查询Mongoose API

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