概述
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 使用,这一篇就够了
最后
以上就是大方小懒猪为你收集整理的Node.js集成MongoDB之Mongoose详细入门的全部内容,希望文章能够帮你解决Node.js集成MongoDB之Mongoose详细入门所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复