参考
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); 创建集合,即table
db.getCollection("test");
show collections; 集合有可能在insert时会自动创建,如下collection1
db.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可对数据库进行操作。用代码解释下:
// 定义Schema
UserSchema = new mongoose.Schema({
user: {// 真实姓名
type: String
},
password: { // 密码
type: String
},
shopCar: {
type: Array,
// default: []
}
}, { collection: 'user'});
// 定义Model
let UserModel = mongoose.model('user', UserSchema);
// 新增一条记录:写法1
let TestEntity = new User({
user : req.query.user,
password : req.query.password,
shopCar: []
});
// 写法2
TestEntity.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();
// or
hero.create();
create
Tank.create({ size: 'small' }, function (err, small) {
if (err) return handleError(err);
// saved!
});
// pass an array of docs
var 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 ID
Todo.findByIdAndUpdate(
// the id of the item to find
req.params.todoId,
// the change to be made. Mongoose will smartly combine your existing
// document with this change, which allows for partial updates too
req.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 errors
if (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:传入回调
```javascript
var query = Person.findOne({ 'name.last': 'Ghost' });
query.select('name occupation'); // 仅返回name、occupation
query.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 doc
Person.
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 builder
Person.
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 animalSchema
animalSchema.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 schema
var personSchema = new Schema({
name: {
first: String,
last: String
}
});
// compile our model
var Person = mongoose.model('Person', personSchema);
// create a document
var bad = new Person({
name: { first: 'Walter', last: 'White' }
});
// 通过虚拟属性的getter,虚拟出全名属性full
personSchema.virtual('name.full').get(function () {
return this.name.first + ' ' + this.name.last;
});
console.log('%s is insane', bad.name.full); // Walter White is insane
app.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 operations
this.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 // 为该字段设置默认值
}
})
```javascript
const 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]
})
});
};