参考
https://laixiazheteng.com/article/page/id/Bj7qgmJ3CJUE
https://laixiazheteng.com/article/page/id/6s0OlGT8AnCk
https://laixiazheteng.com/article/page/id/1NSGRZGU93cy
https://blog.csdn.net/Dong_vs/article/details/106566596
https://www.cnblogs.com/chris-oil/p/9142795.html
MongoDB
概念
简单的说,document是Mongodb中的最小单位,相当于行,并且mongodb中每个文档都有一个_id的键,它在所处的集合中是唯一的。collection相当于表,由document组成。database是由集合组成的数据合集,每个database之间是完全独立的,每一个database都有独立的权限控制。。
命令
show dbs; 查看集合列表use mall; 切换某个数据库,若不存在则新建db.mall.insert({name:"张三",age:"18"}));db.dropDatabase();db.getName();db.stats();db.version();db.getMongo(); 查看当前db的链接地址db.shutdownServer();db.createCollection(name, options); 创建集合,即tabledb.getCollection("test");show collections; 集合有可能在insert时会自动创建,如下collection1db.collection.drop(); 删除集合db.collection1.find().pretty(); 如果集合在数据库中不存在,那么MongoDB 将创建此集合,然后把它插入文档。db.collection1.findOne(); 查询第一条数据db.userInfo.find({age: {$gte: 25}}).count(); 查询符合条件的条数db.collection1.find().sort({"ID":1}).pretty().limit(10).skip(10); 根据ID排序后,跳过10,查询10条,结果为10-19条的数据db.userInfo.find().limit(5); 查询前五条数据db.userInfo.find().skip(10); 查询10条以后的数据db.userInfo.find().limit(10).skip(5); 查询5~10条之间的数据db.collection1.remove({name: 'xxx'}, true); 第二个参数为true则只删除一条db.collection1.update({name: 'xxx'}, {$set: {age: 33}});db.collection1.find().sort({"ID":1});db.userInfo.find().sort({age: -1}); 接受一个文档,其中包含的字段列表连同他们的排序顺序。要指定排序顺序1和-1。 1用于升序排列,而-1用于降序,默认升序
查询语句的一些条件:
- AND: find({name: xxx, age: xxx}),即可实现AND
- OR:db.col.find({$or: [{key1: value1}, {key2:value2}]}).pretty(),即可实现OR

Mongoose CRUD
参考资料
https://itbilu.com/database/mongo/page-2.html
概念
Mongoose中最重要的三个概念:schema,model和entity。三者关系为:schema生成model,model生成entity,model和entity可对数据库进行操作。用代码解释下:
// 定义SchemaUserSchema = new mongoose.Schema({user: {// 真实姓名type: String},password: { // 密码type: String},shopCar: {type: Array,// default: []}}, { collection: 'user'});// 定义Modellet UserModel = mongoose.model('user', UserSchema);// 新增一条记录:写法1let TestEntity = new User({user : req.query.user,password : req.query.password,shopCar: []});// 写法2TestEntity.save(function(error, doc){if(error){console.log("error :" + error);}else{console.log(doc);db.close();}});
增
mongoose没有统一提供一个findOrCreate的功能,也就是不存在就新建,此时可以使用mongoose-findorcreate这个包来解决,它提供了一个model的静态方法
save()、create()
const newTodoObj = new Todo(req.body);newTodoObj.save(err => {if (err) return res.status(500).send(err);return res.status(200).send(newTodoObj);});let hero = new HeroModel({name:'孙尚香',life:5300,type:'射手'});hero.save();// orhero.create();
create
Tank.create({ size: 'small' }, function (err, small) {if (err) return handleError(err);// saved!});// pass an array of docsvar array = [{ type: 'jelly bean' }, { type: 'snickers' }];Candy.create(array, function (err, candies) {if (err) // ...var jellybean = candies[0];var snickers = candies[1];// ...});
insertMany()
HeroModel.insertMany([{name:'刘备',life:'5400',type:'射手'},{name:'关羽',life:'5500',type:'战士'},{name:'张飞',life:'5900',type:'坦克'},],(err, result)=>{if (err) {console.log(err);return;}console.log(result)})
改
更新一条数据,使用update()或者updateOne()方法
const Todo = require("../models/todo");// This would likely be inside of a PUT request, since we're updating an existing document, hence the req.params.todoId.// Find the existing resource by IDTodo.findByIdAndUpdate(// the id of the item to findreq.params.todoId,// the change to be made. Mongoose will smartly combine your existing// document with this change, which allows for partial updates tooreq.body,// an option that asks mongoose to return the updated version// of the document instead of the pre-updated one.{new: true},// the callback function(err, todo) => {// Handle any possible database errorsif (err) return res.status(500).send(err);return res.send(todo);})// 将第一条role为法师的文档的life更新为5300// 第一个参数为条件,第二个参数为更新文档HeroModel.updateOne({role:'法师'},{life:'5300'},(err,result) => {if (err) console.log(err)console.log(result);})
- 使用
updateMany({},{},callback)方法,更新多条数据 - 使用
findByIdAndUpdate(id,{},callback),将返回更新前的数据 - 使用
findOneAndUpdate({},{},callback),将返回更新前的数据
除了上面的方法外,还可以find()或findById()一个方法后,用.save()方法来保存改变。
删
使用remove()方法
// 删除所有匹配条件的数据,删除所有role为法师的数据// 第一个参数为条件HeroModel.remove({role:'法师'},(err,result) => {if (err) console.log(err)console.log(result);})
- 使用
deleteOne({},callback)方法,只删除第一条匹配条件的数据 - 使用
deleteMany({},callback)方法,删除所有匹配条件的数据 - 使用
findByIdAndDelete(id,callback),将返回被删除的数据 - 使用
findByIdAndRemove(id,callback),将返回被删除的数据 - 使用
findOneAndDelete({_id:id},callback),将返回被删除的数据 使用
findOneAndRemove({_id:id},callback),将返回被删除的数据// The "todo" in this callback function represents the document that was found.// It allows you to pass a reference back to the client in case they need a reference for some reason.Todo.findByIdAndRemove(req.params.todoId, (err, todo) => {// As always, handle any potential errors:if (err) return res.status(500).send(err);// We'll create a simple object to send back with a message and the id of the document that was removed// You can really do this however you want, though.const response = {message: "Todo successfully deleted",id: todo._id};return res.status(200).send(response);});
查
find({},callback)方法findOne({},callback)方法Kitten.findOne(// query{color: "white", name: "Dr. Miffles", age: 1},// Only return an object with the "name" and "owner" fields. "_id"// is included by default, so you'll need to remove it if you don't want it.{name: true, owner: true}, // "name owner"// callback function(err, kitten) => {if (err) return res.status(200).send(err)return res.status(200).send(kitten)});
findById(id, [fieldsToReturn], [callback])方法- 进阶:
.where(selector)方法,支持更为复杂的查询,例如用于查询数据库中所有age在1到4之间的小猫。
```javascript // No query passed in means “find everything” Person.find((err, people) => { // Note that this error doesn’t mean nothing was found, // it means the database had an error while searching, hence the 500 status if (err) return res.status(500).send(err) // send the list of all people return res.status(200).send(people); });Kitten.where("age").gte(1).lte(4).exec((err, kittens) => {// Do stuff});
// If query IS passed into .find(), filters by the query parameters Person.find({name: “John James”, age: 36}, (err, people) =>{ if (err) return res.status(500).send(err)
// send the list of all people in database with name of "John James" and age of 36// Very possible this will be an array with just one Person object in it.return res.status(200).send(people);
});
// 第一个参数为条件 HeroModel.findById(“5ed7c432ae5f501e6ca4217b”,(err,doc) => { if (err) console.log(err) console.log(doc); })
<a name="RTRtD"></a>### 总结所以改、删、查相关的api都会返回一个mongoose query对象,该对象默认不会执行查询。可以通过以下两种方式之一执行mongoose查询:1. 传入`callback(error, result)`,若error存在,则result为null,若执行操作成功,则error为null,result会是操作结果。1. 执行`.exec((err, result) => {...})`方法1. 方法1:传入回调```javascriptvar query = Person.findOne({ 'name.last': 'Ghost' });query.select('name occupation'); // 仅返回name、occupationquery.exec(function (err, person) {if (err) return handleError(err);// Prints "Space Ghost is a talk show host."console.log('%s %s is a %s.', person.name.first, person.name.last,person.occupation);});
b. 方法2:promise后用.then().catch((err) => {...})
query.exec().then((person) => {// Prints "Space Ghost is a talk show host."console.log('%s %s is a %s.', person.name.first, person.name.last,person.occupation);}).catch((err) => {handleError(err);})
query变量是一个Query实例。Query允许你使用链式语法构建查询,而不仅是指定JSON对象。以下2个示例是等效的:
// With a JSON docPerson.find({occupation: /host/,'name.last': 'Ghost',age: { $gt: 17, $lt: 66 },likes: { $in: ['vaporizing', 'talking'] }}).limit(10).sort({ occupation: -1 }).select({ name: 1, occupation: 1 }).exec(callback);// Using query builderPerson.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);
Schema
Mongoose是node提供连接 mongodb的一个库。
Mongoose 的一切始于 Schema。每个 schema 都会映射到一个 MongoDB collection ,并定义这个collection里的文档的构成。
说白了,每一个 schema 就是相当于一个collection。Schema不仅定义了文档的属性和类型结构,还定义了文档实例方法和静态模型方法,复合索引和被称之为中间件的生命周期钩子。
是的,Mongoose就是把mongodb封装成了JavaScript对象以供我们在应用中使用。
var blogSchema = new Schema({title: String,author: String,body: String,comments: [{ body: String, date: Date }],date: { type: Date, default: Date.now },hidden: Boolean,meta: {votes: Number,favs: Number}},{ collection: 'artiles' });
第一个参数就是结构对象,每一个键就是一个字段,你可以定义类型/默认值/验证/索引等;第二个参数是可选的(默认是取model的第一个参数加s),用来自定义Collection的名称。
⚠️注意,在mongoose中,我们不需手动去创建Collection,当你操作时,如Collection不存在,会自动创建。
models
创建可供操作的model
我们把定义好的schema转换成需要使用的模型,需要通过mongoose.model(modelName, schema);来实现。如下,collection名字被定义为blogs。
var Blog = mongoose.model('Blog', blogSchema);var blog = new Schema({ name: String, type: String });//模型实例
schema 和 model 的理解
这是最核心的两个概念,Model对应mongodb中的集合 collection,而Schema对应集合的结构,也就是集合都有哪些字段、字段的类型、是否必填、是否有缺省值等。
实例方法
模型的实例是文档(documents)。文档有许多自己内置的实例方法。我们可以自定义文档实例方法。
常用的内置方法如下:
remove、set、get、invalidate、populate、save等
自定义实例方法
animalSchema.methods.findSimilarTypes = function(cb) {return this.model('Animal').find({ type: this.type }, cb);};
静态方法
常用的内置静态方法有:
create、find、findOne等
给model增加静态方法,来封装一些curd操作,语法是:[schema].statics.[yourFunction] = function(xxx) {xxxx};
animalSchema.statics.findByName = function(name, cb) {return this.find({ name: new RegExp(name, 'i') }, cb);};var Animal = mongoose.model('Animal', animalSchema);Animal.findByName('fido', function(err, animals) {console.log(animals);});// Assign a function to the "statics" object of our animalSchemaanimalSchema.statics.findByName = function(name) {return this.find({ name: new RegExp(name, 'i') });};// Or, equivalently, you can call `animalSchema.static()`.animalSchema.static('findByBreed', function(breed) {return this.find({ breed });});
query helper
您还可以像实例方法那样添加查询辅助功能,这是,但对于mongoose的查询。查询辅助方法可以让你扩展mongoose链式查询生成器API。
animalSchema.query.byName = function(name) {return this.find({ name: new RegExp(name, 'i') });};var Animal = mongoose.model('Animal', animalSchema);Animal.find().byName('fido').exec(function(err, animals) {console.log(animals);});
Index
- 索引的优点是提高了查询效率,缺点是在插入、更新和删除记录时,需要同时修改索引,因此,索引越多,插入、更新和删除记录的速度就越慢。
- 通过创建唯一索引,可以保证某一列的值具有唯一性。
mongoose同样也对索引做了处理,在mongoose中定义索引有两种方法。
第一种:直接在schema里面定义,如下所示
var User = mongoose.model('User', {username: {type: String,index: true,unique: true // 创建唯一索引},password: String})
第二种:统一定义索引
var User = mongoose.model('User', {username: {type: String},password: String});User.index({username: 1 / -1 (正向和逆向)})
注:需要注意的是,当应用启动的时候, ,Mongoose会自动为Schema中每个定义了索引的调用ensureIndex,确保生成索引,并在所有的ensureIndex调用成功或出现错误时,在 Model 上发出一个’index’事件。 开发环境用这个很好, 但是建议在生产环境不要使用这个。所以推荐禁用索引:
animalSchema.set('autoIndex', false); //推荐mongoose.connect('mongodb://localhost/blog', { config: { autoIndex: false } });
虚拟属性
虚拟属性 是文档属性,您可以获取和设置但不保存到MongoDB。用于格式化或组合字段,从而制定者去组成一个单一的值为存储多个值是有用的。
可以格式化和自定义组合想要的属性值,类似于vue的computed的意思。
// define a schemavar personSchema = new Schema({name: {first: String,last: String}});// compile our modelvar Person = mongoose.model('Person', personSchema);// create a documentvar bad = new Person({name: { first: 'Walter', last: 'White' }});// 通过虚拟属性的getter,虚拟出全名属性fullpersonSchema.virtual('name.full').get(function () {return this.name.first + ' ' + this.name.last;});console.log('%s is insane', bad.name.full); // Walter White is insaneapp.route('/api/users/name').get(usersController.getFullName);exports.getFullName = (req, res) => {UsersModel.findById(req.query.id, (err, result) => {if (err) {return err.status(400).send({message: '用户不存在',data: []});} else {console.log(result);res.jsonp(result.name.full);}})}

注意,这里的虚拟属性并没有存入数据库,所以如果是直接获取,是获取不到值的。
验证器
在你save数据之前, 你可以对数据进行一些列的validation,防止破坏了数据完整性
几种内置的验证器:
- required:必须填写的字段
- Number
- min\max:给number类型做数据限制
- String
- enum:列出枚举值
- match:匹配正则表达式的,
match:/^a/ - maxlength/minlength:匹配字符串长度的
var breakfastSchema = new Schema({eggs: {type: Number,min: [6, 'Too few eggs'], // 第二个参数为错误理由max: 12},bacon: {type: Number,required: [true, 'Why no bacon?']},drink: {type: String,enum: ['Coffee', 'Tea']}});var Breakfast = db.model('Breakfast', breakfastSchema);var badBreakfast = new Breakfast({eggs: 2,bacon: 0,drink: 'Milk'});var error = badBreakfast.validateSync();assert.equal(error.errors['eggs'].message,'Too few eggs');assert.ok(!error.errors['bacon']);assert.equal(error.errors['drink'].message,'`Milk` is not a valid enum value for path `drink`.');badBreakfast.bacon = null;error = badBreakfast.validateSync();assert.equal(error.errors['bacon'].message, 'Why no bacon?');
还可以通过在Schema定义中加入validate字段来自定义验证器,cat.save(function(error) { //自动执行,validation });时会自动执行验证。
const UsersSchema = new Schema({...phone: {type: String,validate: { // 自定义验证器validator: function(data) {return /\d{3}-\d{3}-\d{4}/.test(data);},message: '`{PATH}:{value}`必须是有效的11位手机号码!'},required: [true, 'User phone number required']}}, {collection: 'users'});
联表查询
如果你使用过mysql,肯定用过join,用来联表查询,但mongoose中并没有join,不过它提供了一种更方便快捷的办法,Population。
Mongoose 的 populate() 可以连表查询,即在另外的集合中引用其文档。Populate() 可以自动替换 document 中的指定字段,替换内容从其他 collection 中获取。
我们只要提供某一个collection的_id , 就可以实现完美的联合查询. population 用到的关键字是: ref 用来指明外联的数据库的名字,写在schema中。
操作方法如下:
- refs:创建 schema 时,可给该 Model 中关联存储其它集合 _id 的字段设置 ref 选项。ref 选项告诉 Mongoose 在使用 populate() 填充的时候使用哪个 Model。
const mongoose = require("mongoose");const { Schema, model } = mongoose;const answerSchema = new Schema({__v: { type: Number, select: false },content: { type: String, required: true },answerer: {type: Schema.Types.ObjectId,ref: "User",required: true,select: false},questionId: { type: String, required: true },voteCount: { type: Number, required: true, default: 0 }},{ timestamps: true });module.exports = model("Answer", answerSchema);
上例中 Answer model 的 answerer 字段设为 ObjectId 数组。 ref 选项告诉 Mongoose 在填充的时候使用 User model。所有储存在 answerer 中的 _id 都必须是 User model 中 document 的 _id。
ObjectId、Number、String 以及 Buffer 都可以作为 refs 使用。 但是最好还是使用 ObjectId。在创建文档时,保存 refs 字段与保存普通属性一样,把 _id 的值赋给它就好了。
const Answer = require("../models/answers");async create(ctx) {ctx.verifyParams({content: { type: "string", required: true }});const answerer = ctx.state.user._id;const { questionId } = ctx.params;const answer = await new Answer({...ctx.request.body,answerer,questionId}).save();ctx.body = answer;}
middleware
mongoose提供了pre和post两个中间件
- pre: 在指定方法执行之前绑定。 中间件的状态分为 parallel和series.
- post: 相当于事件监听的绑定
这里需要说明一下, 中间件一般仅仅只能限于在几个方法中使用. (但感觉就已经是全部了)
- doc 方法上: init,validate,save,remove;
- model方法上: count,find,findOne,findOneAndRemove,findOneAndUpdate,update
在你调用 model.save方法时, 他会自动执行pre. 如果你想并行执行中间件, 可以设置为:// series执行, 串行var schema = new Schema(..);schema.pre('save', function(next) {// exe some operationsthis.model.next(); // 这里的next()相当于间执行权给下一个pre});
schema.pre('save', true, function(next, done) {// 并行执行下一个中间件next();});

例子
schema例子
```javascript const mongoose = require(‘mongoose’);
let HeroSchema = new mongoose.Schema({
name: {
required:true, // 设置该字段为必传字段
type: String, // 字段类型
trim:true, //修饰符,去掉两端的空格
set(name) { //自定义修饰符
return “WZRY·”+name;
},
index: true // 为该字段创建索引
// unique:true // 为该字段创建唯一索引
// 创建索引可加快查询速度
},
life: {
required: true,
type: Number,
min: 4000, // 设置该字段最大值,只能用于Number类型
max: 6000 // 设置该字段最小值,只能用于Number类型
},
type: {
required: true,
type: String,
enum: [“射手”,”辅助”,”法师”,”战士”,”刺客”,”坦克”]
// 枚举,该字段必须满足枚举值,只能用于String类型
}
price:{
required: true,
type: Number,
validate: function(price){ // 自定义验证器,返回boolean值
return price > 0 && price < 20000;
}
}
create_time: {
type: Date,
default: Date.now // 为该字段设置默认值
}
})
```javascriptconst UserSchema = mongoose.Schema({name: String,age: Number,sex: Boolean});// Schema的静态方法UserSchema.statics.findUserByAge = function (age) {// 用then来拿到结果,如findUserByAge(21).then((res) => {})。// 也可以在find第二个参数传回调函数,如下return this.find({'age': age});}bookSchema.statics.FindbookByName = (name,callback) => { // 根据name查找图书this.model("Book").find({"name": name}, callback);}// 调用exports.edit = (req,res,next) => { // 查找到要修改的图书显示Book.FindbookByName(req.query.name,function(err,result){ res.render("edit",result[0]); });}// id是独一无二的 mongo分配的 根据id去修改图书的内容即可export.doedit = (req,res,next) => {Book.update({id: req.query.id}, {name: req.query.name,author: req.query.author,price:req.query.price},(err,result)=>{if(err) {res.send(err)}res.send('修改成功')})}// Schema的实例方法UserSchema.methods.findUserByName = function(name) {return this.model('User').find({ name: name });};// 创建一个Model,第三参数指定mongodb中collection的名字,如果不传则默认为变为复数(即Users)const User = mongoose.model('User', UserSchema, 'user');// 创建一条数据// 1. 直接用User创建User.create({name: 'zhangsan', age: 27, sex: true}, function(err, info) {console.log(info);});// 2. 新创建一个对象再添加数据到数据库// 再次说明下现在mongoose默认是支持promise方式的,可以使用then 或 回调函数的方式var person = new User({name: 'lisi', age: 20, sex: false});person.save().then(res => {console.log(res);})// 调用Schema中定义的静态方法,可以通过User直接调User.findUserByAge(21);// 调用Schema中定义的实例方法方法,先实例化再调const user = new User();user.findUserByName('zhangsan');// 删除数据,删除符合条件的一条User.deleteOne({name: 'lisi'}).then();// 删除符合条件的所有数据User.deleteMany({age: 20}).then();// 注意:remove()方法已经被弃用了!
CURD操作
对博客文章进行crud操作的示范
const mongoose = require('mongoose');const ArticlesModel = mongoose.model('articles');mongoose.Promise = global.Promise;const commonFunction = require('../common/common_function');const _ = require('lodash');exports.get = (req, res) => {const articleId = req.query['id'];ArticlesModel.findById(articleId, (err, result) => {if (err) {return res.status(400).send({message: '查找失败',data: []});} else {res.jsonp({data: [result]});}});};exports.add = (req, res) => {req.body['articleId'] = commonFunction.getRandom();req.body['modifyOn'] = new Date();const article = new ArticlesModel(req.body);article.save((err) => {if (err) {return res.status(400).send({message: '新增失败',data: []});} else {res.jsonp({data: [article]});}})};exports.remove = (req, res) => {const id = req.query['id'];ArticlesModel.remove({'_id': id}, (err) => {if (err) {return res.status(400).send({message: '删除失败',data: []});} else {res.jsonp({status: 1});}})};exports.update = (req, res) => {const id = req.body['id'];ArticlesModel.findById(id, (err, result) => {if (err) {return res.status(400).send({message: '更新失败',data: []});} else {delete req.body['id'];const articles = _.extend(result, req.body);articles.save((err, result) => {if (err) {return res.status(400).send({message: '更新失败',data: []});} else {res.jsonp({data: [articles]});}})}})};/*** 进阶篇*/exports.getAuthorByArticleid = (req, res) => {ArticlesModel.findById(req.query.id).populate('by', 'name -_id').exec(function (err, story) {if (err) {return res.status(400).send({message: '更新失败',data: []});}res.jsonp({data: [story]})});};


