简单介绍
简单使用
我的运行命令: mongod --dbpath "D:\Program Files\MongoDB\Server\6.0\data\db"
创建和插入数据
insertOne插入一个数据,insertMany插入多个
db代表当前的库
// 新建一个库
> use natours-test
//插入
natours-test> db.tours.insertOne({name: "The Forest Hiker",price:297,rating: 4.7})
- 返回:
{
acknowledged: true,
insertedId: ObjectId("63f384f60b988fe7c3caf7e6")
}
//使用find返回创建的对象
natours-test> db.tours.find()
- 返回:
[
{
_id: ObjectId("63f384f60b988fe7c3caf7e6"),
name: 'The Forest Hiker',
price: 297,
rating: 4.7
}
]
//显示所有的
natours-test> show dbs
admin 40.00 KiB
config 108.00 KiB
local 72.00 KiB
natours-test 40.00 KiB
//会显示所有创建的文档
natours-test> show collections
返回
tours
CRUD操作
插入
插入多个,且不同结构
natours-test> db.tours.insertMany([{name:"The Sea Explorer",price: 497,rating:4.8},{name:"The Show Adventurer",price : 997,rating:4.9,difficulty: "easy"}])
natours-test> db.tours.find()
[
{
_id: ObjectId("63f384f60b988fe7c3caf7e6"),
name: 'The Forest Hiker',
price: 297,
rating: 4.7
},
{
_id: ObjectId("63f38d8c14709b1a5cdb255f"),
name: 'The Sea Explorer',
price: 497,
rating: 4.8
},
{
_id: ObjectId("63f38d8c14709b1a5cdb2560"),
name: 'The Show Adventurer',
price: 997,
rating: 4.9,
difficulty: 'easy'
}
]
查询
mongodb works with object
- find()
传入对象进行搜索
natours-test> db.tours.find({name: "The Forest Hiker"})
[
{
_id: ObjectId("63f384f60b988fe7c3caf7e6"),
name: 'The Forest Hiker',
price: 297,
rating: 4.7
}
]
传入一个新对象{$lte:500}—>小于运算符less then or echo
lt:less then
gte:greater than or echo
or:{$or: [{price: {$lt: 500}},{rating:{$gte:4.8}}]}
投影:筛选出特定需要展示的元素,设置为1
natours-test> db.tours.find({price:{$lte:500}})
[
{
_id: ObjectId("63f384f60b988fe7c3caf7e6"),
name: 'The Forest Hiker',
price: 297,
rating: 4.7
},
{
_id: ObjectId("63f38d8c14709b1a5cdb255f"),
name: 'The Sea Explorer',
price: 497,
rating: 4.8
}
]
两个条件的查询
natours-test> db.tours.find({price:{$lt:500},rating:{$gte:4.8}})
----
[
{
_id: ObjectId("63f38d8c14709b1a5cdb255f"),
name: 'The Sea Explorer',
price: 497,
rating: 4.8
}
]
或运算:
natours-test> db.tours.find({$or: [{price: {$lt: 500}},{rating:{$gte:4.8}}]})
----
[
{
_id: ObjectId("63f384f60b988fe7c3caf7e6"),
name: 'The Forest Hiker',
price: 297,
rating: 4.7
},
{
_id: ObjectId("63f38d8c14709b1a5cdb255f"),
name: 'The Sea Explorer',
price: 497,
rating: 4.8
},
{
_id: ObjectId("63f38d8c14709b1a5cdb2560"),
name: 'The Show Adventurer',
price: 997,
rating: 4.9,
difficulty: 'easy'
}
]
投影
natours-test> db.tours.find({$or: [{price: {$gt: 500}},{rating:{$gte:4.8}}]},{name:1})
-----------------
[
{
_id: ObjectId("63f38d8c14709b1a5cdb255f"),
name: 'The Sea Explorer'
},
{
_id: ObjectId("63f38d8c14709b1a5cdb2560"),
name: 'The Show Adventurer'
}
]
更新
- db.tours.updateOne(),传入要改的对象和更改的数据,仅修改第一个匹配的数据,使用set进行更新
- db.tours.replaceOne() ,传入要替换的对象和替换的数据
natours-test> db.tours.updateOne({name:"The Show Adventurer"},{$set: {price:597}})
----------
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
可以用这个创建之前没有的元素,many将修改匹配的多处
natours-test> db.tours.updateMany({price : {$gt:500},rating:{$gte:4.8}},{$set:{premium:true}})
删除
无法撤销!
- db.tours.deleteMany({rating:{$lt: 4.8} }), 删除符合条件的对象
- db.tours.deleteMany({ }), 传入空对象以删除所有对象
使用Campass可视化界面进行CRUD
使用Atlas云数据库
创建一个云数据库,然后将密码填入环境变量保存
使用campass进行链接云数据库
连接express
下载 - npm i mongoose@5
配置环境变量时记得在url后面添加自己想要保存的库名:mongodb.net/natours?retryWrites=true
//登录
const DB = process.env.DATABASE.replace(
'<password>',
process.env.DATABASE_PASSWORD
);
mongoose
.connect(process.env.DATABASE_LOCAL, {
// .connect(DB, {
useCreateIndex: true,
useNewUrlParser: true,
useFindAndModify: false,
useUnifiedTopology: true,
})
.then((con) => {
console.log(con.connections);
console.log('DB CONNECTION SUCCESSFUL');
});
为什么Mongoose
使用express和mongoose创建一个文件
创建范式
- new mongoose.Schema传入两个参数
一个属性可以有多种要求
- type
- required:bool&错误提示
- unique:唯一
- trim:自动剪裁
- maxlength,minlength:大小&报错信息
- enum:可接受的字段
- default:默认数据
- select:判断是否需要显示到查询中
- validate:创建一个函数以自定义数据验证,传入一个对象:validator为函数,msg为错误信息,并且信息可以访问到传入的值:{VALUE}
const tourSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'A tour must have a name'],
unique: true,
trim: true,
maxlength: [40, 'A tour name must have less or equal then 40 characters'],
minlength: [10, 'A tour name must have more or equal then 10 characters']
// validate: [validator.isAlpha, 'Tour name must only contain characters']
},
slug: String,
duration: {
type: Number,
required: [true, 'A tour must have a duration']
},
maxGroupSize: {
type: Number,
required: [true, 'A tour must have a group size']
},
difficulty: {
type: String,
required: [true, 'A tour must have a difficulty'],
enum: {
values: ['easy', 'medium', 'difficult'],
message: 'Difficulty is either: easy, medium, difficult'
}
},
ratingsAverage: {
type: Number,
default: 4.5,
min: [1, 'Rating must be above 1.0'],
max: [5, 'Rating must be below 5.0']
},
ratingsQuantity: {
type: Number,
default: 0
},
price: {
type: Number,
required: [true, 'A tour must have a price']
},
priceDiscount: {
type: Number,
validate: {
validator: function(val) {
// this only points to current doc on NEW document creation
return val < this.price;
},
message: 'Discount price ({VALUE}) should be below regular price'
}
},
summary: {
type: String,
trim: true,
required: [true, 'A tour must have a description']
},
description: {
type: String,
trim: true
},
imageCover: {
type: String,
required: [true, 'A tour must have a cover image']
},
images: [String],
createdAt: {
type: Date,
default: Date.now(),
select: false
},
startDates: [Date],
secretTour: {
type: Boolean,
default: false
}
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true }
}
);
const Tour = mongoose.model('Tour', tourSchema);
插入数据
生产一条数据
创建文档然后添加数据
const testTour = new Tour({
name: 'The Forest Hiker',
rating: 4.7,
price: 497,
});
testTour
.save()
.then((doc) => {
console.log(doc);
})
.catch((err) => {
console.log('==========ERROR:=======', err);
});
建立新数据的方法
- Tour.create({}) 直接在文档中创建一个数据
MVC架构
重构之前的路由:
exports.createTour = async (req, res) => {
try {
//使用create方法直接创建对象
const newTour = await Tour.create(req.body);
res.status(200).json({
status: 'success',
data: {
tour: newTour,
},
});
} catch (err) {
res.status(400).json({
status: 'fail to create',
msg: err,
});
}
};
此时在body中的信息如果不是范式中规定的将被忽略
搜索:
const tour = await Tour.findById(req.params.id);
//Tour.findOne({ _id: req.params.id })
更新:
const tour = await Tour.findByIdAndUpdate(req.params.id, req.body, {
new: true,
});
方法大全:https://mongoosejs.com/docs/queries.html
脚本导入json到数据库
自动跑函数:使用process.argv参数读取
if (process.argv[2] === '--import') {
importData();
} else if (process.argv[2] === '--delete') {
deleteData();
}
console.log(process.argv);
命令:node .\import-dev-data.js --import
返回:
[
'D:\\study\\nodejs\\node.exe',
'D:\\dev-data\\data\\import-dev-data.js',
'--import'
]
这样就实现了自动调用
读取文件与删除库
const tours = fs.readFileSync(`${__dirname}/tours-simple.json`, 'utf-8');
const importData = async () => {
try {
await Tour.create(JSON.parse(tours));
console.log('Data successfully loaded!');
process.exit();
} catch (err) {
console.log(err);
}
};
const deleteData = async () => {
try {
await Tour.deleteMany();
console.log('Data successfully delete!');
process.exit();
} catch (err) {
console.log(err);
}
};
通过url查询
例:/api/v1/tours?duration=5&difficultu=easy
使用:
console.log(req.query);
控制台:
{ duration: '5' }
GET /api/v1/tours?duration=5 200 67.560 ms - 9558
两种查询方式:find和where
// const tours = await Tour.find({
// duration: '5',
// });
const tuors = await Tour.find()
.where('duration')
.equals(5)
.where('difficulty')
.equals('easy');
实践
const tours = await Tour.find(req.query);
使用过滤器过滤不需要的参数
//深拷贝
const queryObj = { ...req.query };
const excludeFields = ['page', 'sort', 'limit', 'fields'];
excludeFields.forEach((el) => delete queryObj[el]);
console.log(req.query, queryObj);
const tours = await Tour.find(queryObj);
使用find方法会返回一个query对象,上面有许多原型方法,因此可以做链条函数,但是如果find被异步执行,此时后面的链条函数将失效,因此我们需要:
//创建一个query
const queryObj = { ...req.query };
const excludeFields = ['page', 'sort', 'limit', 'fields'];
excludeFields.forEach((el) => delete queryObj[el]);
console.log(req.query, queryObj);
const query = Tour.find(queryObj);
// const query = Tour.find()
// .where('duration')
// .equals(5)
// .where('difficulty')
// .equals('easy');
//执行查询
const tours = await query;
//发送请求
复杂查询
查询多项且带条件find
结果:
{
difficulty: 'easy',
page: '5',
duration: { gte: '5' },
price: { lt: '1500', gte: '100' }
}
使用正则表达式进行:
console.log(req.query);
//深拷贝
//创建一个query
// filtering
const queryObj = { ...req.query };
const excludeFields = ['page', 'sort', 'limit', 'fields'];
excludeFields.forEach((el) => delete queryObj[el]);
//advanced filtering
let queryStr = JSON.stringify(queryObj);
queryStr = queryStr.replace(/\b(gte|gt|lte|lt)\b/g, (match) => `$${match}`);
console.log(JSON.parse(queryStr));
const query = Tour.find(JSON.parse(queryStr));
//{difficulty:'easy',duration:{$gte:5}}
//{ difficulty: 'easy', page: '5', duration: { gte: '5' } }
//gt gte lt lte
排序和筛选字段 sort、select
使用负号进行反选
//sort
if (req.query.sort) {
const sortBy = req.query.sort.split(',').join(' ');
console.log(sortBy);
//由于find返回一个query对象,因此可以进行sort等链接操作
query = query.sort(sortBy); //逆序仅需要传入负值
} else {
query = query.sort('-createdAt');
}
//限制字段
if (req.query.fields) {
const fields = req.query.fields.split(',').join(' ');
query = query.select(fields);
} else {
query = query.select('-__v');
}
分页操作:skip、 limit
//分页:跳过前几页的操作,然后限制显示
const page = req.query.page * 1 || 1;
const limit = req.query.limit * 1 || 100;
const skip = (page - 1) * limit;
query = query.skip(skip).limit(limit);
if (req.query.page) {
const numTours = await Tour.countDocuments();
if (skip >= numTours) throw new Error('This page does not exist');
}
设置特定的路由进行特定查询
路由:
router
.route('/top-5-cheap')
.get(tourController.aliasTopTours, tourController.getAllTour);
设置参数进行特殊查询
exports.aliasTopTours = (req, res, next) => {
req.query.limit = '5';
req.query.sort = '-ratingAverage,price';
req.query.fields = 'name,price,ratingAverage,summary,difficulty';
next();
};
分离方法与controller,将其导入另一个文件
mongoDB聚合管道
使用聚合管道可对集合中的文档进行变换和组合
管道传入一个对象数组,数据库的数据将按传入的对象的顺序进行变化,后面的对象可以用前面定义好的变量
id将根据不同的取值创建不同的组,变量前面全部需要$
- 各类字段参考手册官网
exports.getTourStats = async (req, res) => {
try {
const stats = await Tour.aggregate([
{
$match: { ratingsAverage: { $gte: 4.5 } },
},
{
$group: {
_id: '$difficulty',
numTours: { $sum: 1 },
numRating: { $sum: '$ratingsQuantity' },
avgRating: { $avg: '$ratingsAverage' },
},
},
]);
const plan = await Tour.aggregate([
{
$unwind: '$startDates'
},
{
$match: {
startDates: {
$gte: new Date(`${year}-01-01`),
$lte: new Date(`${year}-12-31`)
}
}
},
{
$group: {
_id: { $month: '$startDates' },
numTourStarts: { $sum: 1 },
tours: { $push: '$name' }
}
},
{
$addFields: { month: '$_id' }
},
{
$project: {
_id: 0
}
},
{
$sort: { numTourStarts: -1 }
},
{
$limit: 12
}
console.log(stats);
};
添加虚拟字段
- fat model and thin controller philosophy
向数据库添加一个虚拟字段并显示出来,但是添加的字段无法被查询
用途:将不必须加入数据库但展示需要的内容添加进去
必须使用func,不可以使用箭头函数,因为需要this字段,它指向文档本身
tourSchema.virtual('durationWeeks').get(function() {
return this.duration / 7;
});
Mongoose 中间件(hook)
文档
类似于express的中间件,同样拥有next方法
分为前中间件和后中间件
document middleware
前中间件this指向文档,后中间件可以传入文档,但是this不指向文档
// DOCUMENT MIDDLEWARE: runs before .save() and .create()
tourSchema.pre('save', function(next) {
this.slug = slugify(this.name, { lower: true });
next();
});
// tourSchema.pre('save', function(next) {
// console.log('Will save document...');
// next();
// });
// tourSchema.post('save', function(doc, next) {
// console.log(doc);
// next();
// });
query middleware
也分为前与后
此时的this为一个query对象,可以向其添加自定义属性
由于find有find,findone,findmany,因此使用正则表达式可以匹配所有的方法
post获取到query返回的document,他的this可以获取到之前添加的属性
// QUERY MIDDLEWARE
// tourSchema.pre('find', function(next) {
tourSchema.pre(/^find/, function(next) {
this.find({ secretTour: { $ne: true } });
this.start = Date.now();
next();
});
tourSchema.post(/^find/, function(docs, next) {
console.log(`Query took ${Date.now() - this.start} milliseconds!`);
next();
});
aggregation middleware
// AGGREGATION MIDDLEWARE
tourSchema.pre('aggregate', function(next) {
this.pipeline().unshift({ $match: { secretTour: { $ne: true } } });
console.log(this.pipeline());
next();
});
const To
数据验证
- fat model and thin controller philosophy
在创建范式时就定义好数据的需求,详见上文创建范式
在更新数据时使用需要加上字段:runValidators: true
使用npm库: validator.js