mongoose 操作mongodb数据库

1,608 阅读8分钟

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

一.如何使用moogose

1.新建文件夹

image.png

2.在mongoose01文件终端里面npm init --yes安装package.json用来管理依赖

3.cnpm i mongoose --save安装mongoose模块

4.在mongoose01里面新建app.js文件

开始搞
      
//1.导入模块mongoose
const mongoose = require('mongoose'); //cnpm i mongoose --save  操作数据库
//2.调用mongoose连接数据库
mongoose.connect('mongodb://127.0.0.1:27017/eggcms');

//3.操作数据库集合Users    定义一个Schema  里面的的对象和数据库表里边的字段需要一一对应
var UserSchema = mongoose.Schema({
    name:String,
    age:Number,
    status:Number
})

//4.定义数据库模型  
//第一个参数 1:首字母大写 2.和数据库里面的表名字对应起来    第二个参数就是UserSchema  第三个参数进行指定
//这个模型会和数据相同名称复数的表相连接
// var User = mongoose.model('User', UserSchema);
var User = mongoose.model('User', UserSchema, "user");

//5.查询Users表的数据
// User.find({}, function(err, doc){
//     if(err) {
//         console.log(err);
//         return;
//     };
//     console.log(doc);
// })

//6.增加数据
//6.1 实例化model 通过实例化user创建数据
//6.2 实例.save()增加数据

// var u = new User({
//     name:"lisi",
//     age:18,
//     status:1
// })
// u.save(function(err){  //执行添加
//     if(err) {
//         console.log(err);
//         return;
//     }
//     console.log("添加数据成功");
// }); 

image.png

   //7.更新数据
 //7.1定义一个新闻Schema
 var NewsSchema = mongoose.Schema({
     title:String,
     author:String,
     content:String,
     status:Number
 })
//7.2定义操作数据库的Model
var News = mongoose.model("News",NewsSchema,"news");
//7.3增加数据
var news = new News({
     title:"更文挑战",
     author:"leilei",
     content:"我是内容",
     status:1
})
news.save();
//7.4修改数据
News.updateOne({"_id":"617fdae8e3c05d76df672227"},{"title":"修改更文挑战"},(err, doc)=> {
    if(err) {
        console.log(err);
        return;
    }
    console.log(doc);
})

这是添加的新闻 image.png

这是修改后的新闻,注意修改前先把新增那个段代码注释掉

image.png

//8删除数据
News.deleteOne({"_id":"617fdae8e3c05d76df672227"},(err,result)=>{
    if(err) {
        console.log(err);
        return;
    }
    console.log(result);
})

删除后发现数据里面的那条数据不见啦啦啦

image.png

二.mongoose模块化

这样其实看着不是很清晰,现在都是模块化开发 我们就把mongoose模块封装一下: 在代码文件里新建一个model文件夹用来存放封住的模块 db.js、news,js、user.js 他们的代码分别是

db.js

//这个模块实现的功能是连接数据库

//1.导入模块mongoose
const mongoose = require('mongoose'); //cnpm i mongoose --save  操作数据库

//2.调用mongoose连接数据库  第二个参数可以消除连接数据库的警告错误,不加都无所谓只是看着舒服一点   第三个参数可以监听数据库有没有连接成功
mongoose.connect('mongodb://127.0.0.1:27017/eggcms', { useNewUrlParser: true } ,function(err){
    if(err) {
        console.log(err);
        return
    }
    console.log("数据库连接成功");
});

module.exports=mongoose;

news,js

var mongoose=require('./db.js');//导入db.js模块连接数据库
// 定义一个Schema
var NewsSchema = mongoose.Schema({
    title:String,
    author:String,
    content:String,
    status: {
        type:Number,
        default:1
    }
})
//定义数据库模型  
var NewsModel = mongoose.model('News',NewsSchema,'news');

//暴露出去
module.exports = NewsModel;

user.js

var mongoose=require('./db.js');//导入db.js模块连接数据库
// 定义一个Schema
var UserSchema = mongoose.Schema({
    name:String,
    age:Number,
    status:{
        type:Number,
        default:1
    }
})
//定义数据库模型  
var UserModel = mongoose.model('User',UserSchema,'user');

//暴露出去
module.exports = UserModel;

这样其实就已经封装好了 开始搞 在app.js里面进行mongo数据库的增删改查

//模块化开发
var UserModel=require('./model/user.js');
var NewsModel = require('./model/news.js');
UserModel.find({},(err,docs)=>{
    if(err) {
        console.log(err);
        return
    }
    console.log(docs);
})
NewsModel.find({},(err,docs)=>{
    console.log(docs);
})

增加数据
var user = new UserModel({
    name:"zhangsan",
    age:40,
})
user.save((err)=>{
    if(err) {
        console.log(err);
        return;
    }
    console.log("增加成功");
});

修改数据
UserModel.updateOne({"_id":"617fd4adc1dbca97f30eebfd"},{name:"leilei666"},(err,result)=>{
    if(err) {
        console.log(err);
        return;
    }
    console.log(result);
})

删除数据
UserModel.deleteOne({"_id":"617fd4adc1dbca97f30eebfd"},(err,result)=>{
    if(err) {
        console.log(err);
        return;
    }
    console.log(result);
})

有同学就要问了这个多次连接数据库,性能是不是很拉闸 接下来讲一下性能的问题,在模块封装结束后我们在app.js文件中写

console.time("user");
var UserModel=require('./model/user.js');
console.timeEnd("user")
console.time("news");
var NewsModel = require('./model/news.js');
console.timeEnd("news")

这里对比一下执行时间

image.png 经过多次实验发现第二次调用时完全是瞬间反应,很快很快 实际情况就是你虽然调用了两次数据库连接,但程序里面只调用一次,这个已经优化得很好了 至于他是怎么优化得,目前我暂时看不懂他的底层代码,但类似于设计模式里面的单例模式。


三.mongoose预定义模式修饰符

顾名思义就是预判到别人在传入数据时候胡乱操作比如说输入内容时候打几个空格之类的离谱操作

var news = new NewsModel({
    title:" mongoose预定义模式修饰符    ", //注意这里标题有空格
    author:"leilei",
    content:"我是个内容",
})
news.save((err)=>{
    if(err) {
        return console.log(err);
    };
    NewsModel.find({},(err, docs)=>{
        if(err) {
            return console.log(err);
        }
        console.log(docs);
    })

});

那么接下来就是解决这些问题 在先前疯转的news.js里面这样写

var mongoose=require('./db.js');//导入db.js模块连接数据库
// 定义一个Schema
var NewsSchema = mongoose.Schema({
    title:{
        type:String,
        trim:true //定义 mongoose模式修饰符去掉空格
    },
    author:String,
    content:String,
    status: {
        type:Number,
        default:1 //表示默认添加的值
    }
})
//定义数据库模型  
var NewsModel = mongoose.model('News',NewsSchema,'news');

//暴露出去
module.exports = NewsModel;

再次跑一下代码

image.png 明显这两个还是有差距的

当然要满某些蛋疼的需求,这个肯定是不够的,官方提供的也不是很够用 这里就需要用到Mongoose Getters Setters 自定义修饰符 就比如说现在要满足一个需求为

     www.leilei.com         保存为http://www.leilei.com
     http://www.leilei.com  保存为http://www.leilei.com
           

那news里面就应该这样写

var mongoose = require('./db.js');//导入db.js模块连接数据库
// 定义一个Schema
var NewsSchema = mongoose.Schema({
    title: {
        type: String,
        trim: true //定义 mongoose模式修饰符去掉空格
    },
    author: String,
    pic: String,
    redirect: {
        type: String,
        set(parmas) { //增加数据的时候对redirect字段进行处理
            //parmas可以获取到redirect的值 返回的数据就是在数据库实际保存的值 
            /*
                www.leilei.com         保存为http://www.leilei.com
                http://www.leilei.com  保存为http://www.leilei.com
            */
            if (!parmas) {
                return '';
            } else {
                if (parmas.indexOf('http://')!=0 && parmas.indexOf('https://')!=0) {
                    return 'https://'+parmas;
                }
                return parmas;
           }
        }
    },
    content: String,
    status: {
        type: Number,
        default: 1 //表示默认添加的值
    }
})
//定义数据库模型  
var NewsModel = mongoose.model('News', NewsSchema, 'news');

//暴露出去
module.exports = NewsModel;

这样不管你怎么写,后台里面redirect就会带有http://

image.png

还有一个get我们不建议使用,因为没啥卵用,即使有用,菜如狗的我也用不到

四.mongoose索引 mongoose内置CURD方法,拓展Mongoose Model的静态方法和实例方法

设置mongoose索引是为了优化我们的查询速度

var mongoose=require('./db.js');//导入db.js模块连接数据库
// 定义一个Schema
var UserSchema = mongoose.Schema({
    name:String,
    age:Number,
    sn:{ //设置索引,我们暂且把sn理解为用户绑定的设备
        type:String,
        index:true,//设置索引的方法
    },
    status:{
        type:Number,
        default:1
    }
})
//定义数据库模型  
var UserModel = mongoose.model('User',UserSchema,'user');

//暴露出去
module.exports = UserModel;

这样添加的user就会有索引sn

image.png 为了保险起见我们在mongo数据库里面查一下是否添加索引成功

image.png 检查完后发现没有问题


创建一个用sn索引进行查询的方法 findBysn()。 user.js模块中这样写

var mongoose=require('./db.js');//导入db.js模块连接数据库
// 定义一个Schema
var UserSchema = mongoose.Schema({
    name:String,
    age:Number,
    sn:{ //设置索引,我们暂且把sn理解为用户绑定的设备
        type:String,
        index:true,//设置索引的方法
        // unique:true,//添加唯一索引
    },
    status:{
        type:Number,
        default:1
    }
})
//静态方法
UserSchema.statics.findBySn= function(sn,cb) {
    //通过find方法获取sn的数据 通过this关键字获取当前的model
    this.find({"sn":sn}, (err, docs)=>{
        cb(err,docs)
    })
}
//实例方法

//定义数据库模型  
var UserModel = mongoose.model('User',UserSchema,'user');

//暴露出去
module.exports = UserModel;

这里要注意静态方法要夹statics

然后在app里面调用

//静态方法
UserModel.findBySn("111111", function (err, docs) {
    if (err) {
        console.log(err);
        return;
    }
    console.log(docs);
})

实例方法

//实例方法 (基本不用)
UserSchema.methods.print = function(){
    console.log("我是一个实例方法");
    console.log(this);
}

然后在app.js里面var u = new UserModel({})然后 user.print();就好了


五.mongoose数据校验

简单来说就是在后台如果多传入了一些字段,数据库里面不会保存,少传入字段数据库会保存,但是你在schema里面定义的字段会为空值(就是这个意思你可以在app.js里面上传name的时候把name去掉试一试)

为了解决这个问题我们用到了数据校验 在UserSchema里面的name里加required:true 就像这样

 name:{
        type:String,//指定类型
        trim:true, //修饰符
        required:true,//必须传入
    },

或者要现在age的范围就这样写

age:{
        type:Number,
        min:0,
        max:15,
    },

枚举,比如说状态只能是0,1,2 那就要使用enum像这样写:

status:{
        type:Number,
        default:1,
        enum:[0,1,2],//status的值必须要012  
    }

规定sn的最大长度要这样写:

 sn:{ //设置索引,我们暂且把sn理解为用户绑定的设备
        type:String,
        index:true,//设置索引的方法
        // unique:true,//添加唯一索引
        maxlength:20,
        minlength:10,
    },

match,用于正则表达式,这样写

 sn:{ //设置索引,我们暂且把sn理解为用户绑定的设备
        type:String,
        index:true,//设置索引的方法
        // unique:true,//添加唯一索引
        maxlength:20,
        minlength:10,
        match:/^sn(.*)/i   //正则不知道的可以去学一学挺有意思的
    },

在讲一下自定义验证器: 也是限制sn的长度大于10

sn:{ //设置索引,我们暂且把sn理解为用户绑定的设备
        type:String,
        index:true,//设置索引的方法
        // unique:true,//添加唯一索引
        // maxlength:20,
        // minlength:10,
        match:/^sn(.*)/i,
        validate: function(sn) {
            return sn.length >= 10;  //这里我们自定义的方法限制了sn的长度
        }
    },

六.mongoose中使用aggregate 聚合管道

这里先给大家看一下我数据库里面的东西

image.png

image.png

其实就是关联表这种操作。聚合管道的方法有几个,具体打印的结果就不写出来了

$project

    db.order.aggregate([{$project:{order_id:1}}]) 管道

$match

//$match
OrderModel.aggregate([
    {
        $lookup: 
        {
            from: "order_item",
            localField: "order_id",
            foreignField: "order_id",
            as:"items"
        }
    },
    {
        $match:{"all_price":{$gte:90}}
    }
],(err,docs)=>{
    if(err) {
        console.log(err);
        return;
    }
    console.log(JSON.stringify(docs));
})

$group

db.order_item.aggregate([{$group:{_id:"$order_id",total:{$sum:"$num"}}}])
db.order_item.aggregate([{$group:{_id:"$order_id",total:{$sum:"$price"}}}])

$sort

db.order.aggregate([{$project:{order_id:1,all_price:1}},{$match:{all_price:{$gte:90}}},{$sort:{all_price:-1}}]) 降序
db.order.aggregate([{$project:{order_id:1,all_price:1}},{$match:{all_price:{$gte:90}}},{$sort:{all_price:-1}}]) 升序

$limit

db.order.aggregate([{$project:{order_id:1,all_price:1}},{$match:{all_price:{$gte:90}}},{$sort:{all_price:1}},{$limit:1}]) 限制

$skip

db.order.aggregate([{$project:{order_id:1,all_price:1}},{$match:{all_price:{$gte:90}}},{$sort:{all_price:1}},{$skip:1}]) 跳过几条

上面都是mongodb的操作,下面我们以lookup为例子示范在代码中该如何写聚合管道关联表查询

$lookup

var OrderModel = require('./model/order.js');
var OrderItemModel = require('./model/order_item.js');
OrderModel.aggregate([
    {
        $lookup: 
        {
            from: "order_item",
            localField: "order_id",
            foreignField: "order_id",
            as:"items"
        }
    }
],(err,docs)=>{
    if(err) {
        console.log(err);
        return;
    }
    console.log(JSON.stringify(docs));
})
下面是一些实操作
//查询order_item找出商品名称是 牛奶的 商品对应的 订单的 订单号 和 订单的总价格
//注意 mongoose中获取ObjectId  用到mongoose.Types.ObjectId
OrderItemModel.aggregate([
    {
        $lookup:
        {
            from: "order",
            localField: "order_id",
            foreignField: "order_id",
            as: "order_info"
        }
    },
    {
        $match:
        {
            // "id":"酸奶"
            _id: mongoose.Types.ObjectId("6177f2d476eef53bcdd769ad")
        }
    }
], (err, docs) => {
    if (err) {
        console.log(err);
        return
    }
    console.log(JSON.stringify(docs));
})

mongoose聚合管道实现多个表的管理查询

ArticleModel.aggregate([
    {
        $lookup: {
            from: "articlecate",
            localField: "cid",
            foreignField: "_id",
            as: "cate"
        }
    },
    {
        $lookup: {
            from: "user",
            localField: "author_id",
            foreignField: "_id",
            as: "user"
        }
    }
], (err, docs) => {
    if (err) {
        console.log(err);
        return
    }
    console.log(JSON.stringify(docs));
})

有需要补充的地方随时欢迎,如果有错误的地方也欢迎大家给我提出来我也学习一下,谢谢