Node.js集成MongoDB之Mongoose详细入门

Mongoose,是MongoDB的一个对象模型工具,是基于node-mongodb-native开发的MongoDB的nodejs驱动,也目前是Node.js操作 MongoDB的首选库。上一篇文章介绍了Node.js官方操作MongoDB的驱动MongoClient

1️⃣ 基础概念

Mongoose的关键概念:

Schema:模型类的骨架,通过Schema可以生成模型类,通过模型类可以生成文档。
Model:由Schema产生的构造器,具有属性和行为。Modal的每一个实例就是MongoDB的一个文档。
Instance:Model的实例,通过new Model()得到,也就是MongoDB的一个文档。

Mongoose是MongoDB的一个对象模型工具,也就是说Mongoose是通过操作对象模型来操作MongoDB的,而Schema是模型类的骨架,换言之,Mongoose 的一切始于 Schema。

2️⃣Schema

每一个Schema对应MongoDB中的一个集合。Schema定义了集合中文档的字段格式。mongoose 出于可维护性和易用性的目的定义Schema来限定文档结构,但是MongoDB没有这个限制,也就是说MongoDB的文档无论什么结构都可以存储。

2.1 定义SchemaTypes

定义Schema的语法有两种,可以直接声明 schema type 为某一种 type(字段类型),或者赋值一个含有 type 属性的对象(选项类型)。

字段类型支持以下JS的标准类型:

  • String
  • Boolean
  • Number
  • Date
  • Object
  • Array

选项类型常用的的属性有:(不同的type,能用的属性也不一样的,具体还请看 官网schematypes

  • type:字段类型
  • default:默认值
  • required:是否必填
  • Index:索引选项
    - background:是否后台创建
    - unique:是否唯一索引
  • min:最小值(type:Number)
  • max:最大值(type:Number)
  • unique:是否唯一索引
const mongoose = require("mongoose");
const Schema = mongoose.Schema;

var schema1 = new Schema({
  name: String //字段类型
});

var schema2 = new Schema({
  name: { type: String ,required:true} //选项类型
});

在这之后,还想给schema添加key值的话,比如条件判断后动态补充字段的情况,可以通过schema.add()方法添加字段。

xxSchema.add({name:String})

2.2 Hello-Mongoose

实在没办法直接解释硬邦邦的概念,先写一个例子,方便更好的总结!看不懂的就看了下文再回来看。

初始化项目

mkdir node_mongoose //新建一个文件夹
cd node_mongoose //进入这个文件夹

npm init -y //生成package.json
npm i mongoose//安装mongodb模块 

至此,项目目录结构应如此:

node_mongoose
	├── node_modules/
	├── package-lock.json
	└── package.json
//hello-mongoose.js
const mongoose = require("mongoose");
const url = "mongodb://localhost";
const Schema = mongoose.Schema;

async function connect() {
  //连接数据库
  try {
    await mongoose.connect(url, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    console.log("connected!");
  } catch (error) {
    console.log("connection fail!");
  }
}

async function createCatModel() {
  //定义Schema
  const catSchema = new Schema({
    name: String,
    kind: { type: String, required: true },
    age: Number,
    food: [Number],
    birth: Date,
  });

  const Cat = mongoose.model("Cat", catSchema);//根据schema生成model
  const LiHua = new Cat({ name: "LiHua", extra: "1" });//新建一个model的实例
  console.log(LiHua);
}

(async () => {
  // await connect();
  await createCatModel();
  mongoose.connection.close();//建议数据库操作完成之后主动断开数据库连接,释放连接资源(非强制)
})();

如果我们以同步的方法打印mongoose.connect(),得到的将是一个Promise { <pending> },也就是处于待定状态的Promise对象,也说明这是一个异步任务,自然可以使用异步函数优雅编程了。至于mongoose.connect()方法里面的某些参数,其实可以不必深入了解,因为你不配置亦可以运行,但是会警告建议你用什么参数加进去,按它说的来把参数选项加进去就可以去掉那些warning了。
在这里插入图片描述

因为目前还没涉及到数据库的读取操作,暂且将连接数据库的方法注释。从打印的结果可得到的结论:

  • schema中没有定义的字段,那么对应model的实例中也不会存储这些不存在的字段
 const LiHua = new Cat({ name: "LiHua", extra: "1" });//extra字段是不存在于catSchema中的
 例证二:
 LiHua.other='1';//other字段是不存在于catSchema中的

执行打印结果都如上图。

  • schema中定义的字段选项为required:true时要在与数据库交互时才能验证
    上文例子中定义了catSchema 的kind字段为必填,我没有填写,居然不报错,不合逻辑啊。于是稍微改动代码,做了数据库读取操作,直接报错,缺少字段kind字段。
  const LiHua = new Cat({ name: "LiHua", extra: "1" });
  LiHua.save(function(err,res){ //保存到数据库
      if(err) throw err;
  })

(async () => {
  await connect(); //开启数据库连接
  await createCatModel();
})();

检验报错

ValidationError: Cat validation failed: kind: Path `kind` is required.

2.3 定义Schema实例方法

documents 是 Models 的实例。 Document 有很多自带的实例方法, 当然也可以自定义我们自己的方法。
方法只是辅助我们操作实例,并不会存到数据库中。

  const xxSchema = new Schema({name:String})
  xxSchema.methods.xxMethod=function(){}

如此,model的实例就可以调用xxMethod了。提醒一下,代码补全时不要按得快,不小心点了xxSchema.method,当时就纳闷了。以下是我的代码:

  catSchema.method.getCatName=function(){
      return this.name;
  }

  const Cat = mongoose.model("Cat", catSchema);
  const LiHua = new Cat({ name: "LiHua" });
  console.log(LiHua.getCatName());

在这里插入图片描述
将以上的method改成methods即可正确打印出预期结果。


打印了catSchema.method,发现它是一个匿名函数;而catSchema.methods是个对象,对象固然可以通过obj.key读取到getCatName方法

console.log(catSchema.method,catSchema.methods);//[Function (anonymous)] { getCatName: [Function (anonymous)] }

查阅资料,发现method也是用来定义方法,只不过定义方法的方式不同于methods。官网传送门

xxSchema.method('xxMethod',function(){})//单个
xxSchema.method({//多个
    purr: function () {}
  , scratch: function () {}
});

注意:不要在自定义方法中使用 ES6 箭头函数,会造成 this 指向错误。原因是:Model就是一个构造函数,再ES6中使用Class来定义了,也就说Model实际就是一个Class,我们知道ES6的Class默认使用严格模式的,而箭头函数自身是没有this的(因为它自身连prototype都没有),其内的this是指向上一级作用域。

catSchema.method('xxMethod',()=>this.name)
const Cat = mongoose.model("Cat", catSchema);

相当于
Class Cat{
	xxMethod: ()=>this.name
}

严格模式的顶级作用域this为undefiend。

2.4 定义Schema静态方法

Schema支持定义静态方法,构建Model后直接通过Model就可调用了。

   //定义静态方法
  catSchema.statics.xxxMethod = function () {
    console.log('static');
  };

  const Cat = mongoose.model("Cat", catSchema);
  Cat.xxxMethod();

同理,也有一个static,用法和method是一样的,就不展开说明了,看上一小节即可。

3️⃣ Model

Models 是从 Schema 编译来的构造函数。 它们的实例就代表着可以从数据库保存和读取的 documents。 从数据库创建和读取 document 的所有操作都是通过 model 进行的。

mongoose.model(name, schema); name与数据库集合名字是相对应的(映射关系),但是关系有点微妙,经我多次测试,得出结论:name参数如果不是以s/S结尾的,model生成的实例save()保存到数据库之后,会生成一个${name}s的集合且将 ${name}的大写字母转为小写,比如mongoose.model('Cat', catSchema);在数据库对应的集合就是cats

var schema = new mongoose.Schema({ name: 'string', size: 'string' });
var Tank = mongoose.model('Tank', schema);//将会对应tanks集合

通过new model()就可以创建一个实例,实例可以采用构造器赋值,也可以对象属性赋值

const xxDoc = new xxModel({name:'xx',...})//构造器赋值 没有值的字段可以不填

const xxDoc = new xxModel();
xxDoc.name = 'xx';//对象属性赋值

3.1 插入文档

  • instance.save() 插入一条文档
  • Model.insertMany() 插入多条文档
  • Model.create() 插入一条或者多条文档
const mongoose = require("mongoose");
const url = "mongodb://localhost";
const Schema = mongoose.Schema;

async function connect() {
  //连接数据库
  try {
    await mongoose.connect(url, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    console.log("connected!");
  } catch (error) {
    console.log("connection fail!");
  }
}
let animalModel = null;
async function insert(operator = "create") { //default "create"
  const animalSchema = new Schema({ //schema
    kind: { type: String, required: true },
    area: [String],
    food: Object,
  });
  if (!animalModel) animalModel = mongoose.model("animal", animalSchema); //model
  try {
    switch (operator) {
      case "save":
        const panda = new animalModel({kind: "panda",area: ["SiChuan"],food: { plant: "bamboo" },}); //instace
        await panda.save();
        await animalModel.find({ kind: { $ne: null } }, function (err, res) {//打印animals集合中kind字段不为null的文档
          console.log(res);
        });
        break;
      case "insertMany":
        const cat = new animalModel({kind: "cat",area: ["Asian", "Africa"],food: { plant: false, meat: true },});
        const pig = new animalModel({kind: "pig",area: ["Asian", "Europe"],food: { plant: true, meat: true },});
        await animalModel.insertMany([cat, pig]);
        await animalModel.find({ kind: { $ne: null } }, function (err, res) {
          console.log(res);
        });
        break;
      case "create":
        const dog = new animalModel({kind: "dog",area: ["Asian"],food: { plant: false, meat: true }});
        await animalModel.create(dog);
        await animalModel.find({ kind: { $ne: null } }, function (err, res) {
          console.log(res);
        });
        break;

      default:
        break;
    }
  } catch (error) {}
}

(async () => {
  await connect();
  await insert("save");
  await insert("insertMany");
  await insert();
  mongoose.connection.close(); //建议数据库操作完成之后主动断开数据库连接,释放连接资源(非强制)
})();

在这里插入图片描述

3.2 查询文档

JSON文档格式查询主要介绍三种情况,因为都和MongoDB查询语法类似。

  • Model.findOne(query) 查询满足条件的第一条文档
  • Model.findById(ID) 查询指定_id的文档
  • Model.find(query).skip().limit() 分页查询
async function query() {
  const animalSchema = new Schema({ //schema
    kind: { type: String, required: true },
    area: [String],
    food: Object,
  });
  let animalModel = mongoose.model("animal", animalSchema); //model
  await animalModel.find({ kind: { $ne: null } }, function (err, res) {
    console.log(res);
  }).skip(2).limit(2); //跳过2条数据,读取后面的数据,但最多读取2条。(每页2条数据,读取第2页)
}

在这里插入图片描述
官网说还支持链式语法构建查询器,如下,可是我操作的时候并没有成功!不知道是我操作失误还是版本不支持了,大家见仁见智吧。

Model.
  find({ occupation: /host/ }).
  where('name.last').equals('Ghost').
  where('age').gt(17).lt(66).
  where('likes').in(['vaporizing', 'talking']).
  limit(10).
  sort('-occupation').
  select('name occupation').
  exec(callback);

3.3 修改文档

  • updateOne(query,update) 修改符合条件的第一个文档
  • updateMany(query,update) 修改符合条件的所有文档

与MongoDB操作有所不同,在MongoDB中update数据项会直接覆盖掉符合条件的整条文档,而此处的只是修改指定的字段,相当于MongoDB中的 $set。

async function update() {
  const animalSchema = new Schema({ //schema
    kind: { type: String, required: true },
    area: [String],
    food: Object,
  });
  let animalModel = mongoose.model("animal", animalSchema); //model
  await animalModel.updateOne({ kind:'dog' }, {"food.plant":true},function (err, res) {
    console.log('updated!');
  })
}

在这里插入图片描述

3.4 删除文档

  • deleteOne(query) 删除符合条件的第一条文档
  • deleteMany(query) 删除符合条件的全部文档
async function deleteDoc() {
  const animalSchema = new Schema({ //schema
    kind: { type: String, required: true },
    area: [String],
    food: Object,
  });
  let animalModel = mongoose.model("animal", animalSchema); //model
  await animalModel.deleteMany({ kind:'dog' }, function (err, res) {
    console.log('deleted!');
  })
}

==========================================================================================

4️⃣结束 (简单引入子文档和中间件概念)

简单的入门就到这了,还有子文档(嵌入文档),中间件等值得我们去学习的。目前没有太多时间供我展开学习,我就简单带过一下!(因为我也没太深入去看)。需要深入学习的,自行链接至官方文档查阅

子文档:某Schema作为另外一个Schema某字段的类型,作用就是mongo没有join连接,通过此方法达到跨集合查询效果

var childSchema = new Schema({ name: 'string' });
var parentSchema = new Schema({
  child: childSchema
});

中间件:中间件是一个函数,可以监听事件的生命周期中的事件,可以实现各种强大功能,比如操作日志记录等等。

async function insert() {
  const animalSchema = new Schema({
    //schema
    kind: { type: String, required: true },
    area: [String],
    food: Object,
  });

  animalSchema.pre("save", function (next) {
    console.log(1);
    next();
  });

  animalSchema.post("save", function () {
    console.log(2);
  });

  let animalModel = mongoose.model("animal", animalSchema); //model
  const duck = new animalModel({ kind: "duck" });
  await duck.save();
}

执行insert函数后,将打印1,2。
pre表示在xx事件前执行,post表示在xx事件后执行,next()表示放行,执行下一个串行中间件(还有并行的)。

至于如何结合Node.js模块化使用,可以看下我上文 Node.js集成MongoDB之MongoClient与模块化模块化部分的内容,其实大同小异,故不累赘。


参考文档:
Mongoose官方文档
关于 mongoose 使用,这一篇就够了

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值