我英文也不好,所以分享给大家,以供参考。这篇文章,是我慢慢看文档抠出来的
MongoDB
文档地址(英文),目前mongoDB已经发布了4.0版本,现有的中文文档全部过时,而且过时的很严重,建议看英文的为主。其实英文文档也没有多难。
CRUD操作
Create
db.集合名.API()
insertOne
插入单条记录
db.users.insertOne({
name: '惣流·明日香·兰格雷'
})
insertMany
插入多条记录
db.users.inserMany([
{name: '绫波丽'},
{name: '惣流·明日香·兰格雷'}
])
Update
db.集合名.API(查询条件, 更新内容, 具体配置)
updateOne
更新满足条件的第一条数据
// 找到id大于1的第一条数据,将name字段更新为惣流·明日香·兰格雷
db.users.updateOne(
{id: {$gt: 1}},
{$set: {
name: '惣流·明日香·兰格雷'
}}
)
updateMany
更新满足条件的全部数据
// 找到id大于0的全局数据,将name字段更新为广末凉子
db.users.updateMany(
{id: ${gt: 0}},
{$set: {
name: '广末凉子'
}}
)
replaceOne
替换满足条件的第一条数据
// 将id为1的数据第一条替换
db.users.replaceOne(
{id: 1},
{name: '惣流・アスカ・ラングレー'}
)
Delete
db.集合名.API()
delete
// 删除集合中的全部数据
db.users.delete({})
deleteOne
// 删除name为美里的第一条数据
db.users.deleteOne({
name: '美里'
})
deleteMany
// 删除id大于1的全部数据
db.users.deleteMany({
id: {$gt: 1}
})
Retrieve
查询操作是增删改查中最为复杂的
基本查询
// 查询集合中的全部文档
db.users.find({})
// 查询集合中name字段包含 "php"的文档
db.users.find({
name: {$regex: /php/gi}
})
And OR
// and查询,id大于1并且name字段等于php或者java的文档
db.users.find({
id: {$gt: 1},
name: {$in: ['php', 'java']}
})
// or查询(短路or,如果第一个条件满足,第二个条件会被忽略)
// name字段包含java或者c++的数据
db.users.find({
$or: [
{name: {$regex: /java/gi}},
{nmae: {$regex: /c++/gi}}
]
})
嵌套文档查询
对于嵌套字段的查询,匹配的查询条件的书写是非常严格的,包括字段的顺序一致时才会被匹配。
db.users.insertMany([
{
name: '广末凉子',
info: {
birthday: 1980,
country: '日本'
}
},
{
name: '本田翼',
info: {
birthday: 1992,
country: '日本'
}
}
])
// 精确查询(注意字段的顺序需要和保存时候的顺序一致)
db.users.find({
info: {
birthday: 1992,
country: '日本'
}
})
// 如果需要对于嵌套文档,使用条件查询而不是精确查询,需要使用点符合
// 查询info.birthday大于1970的数据
db.users.find({
'info.birthday': {$gt: 1970}
})
// 点符号配合and查询
// 查询info.birthday大于1960的并且info.country为日本的数据
db.users.find({
'info.birthday': {$gt: 1960},
'info.country': '日本'
})
数组的查询
db.games.insertMany([
{name: '使命召唤4', tag: ['2007', '战争']},
{name: '黑暗之魂3', tag: ['2016', '魔幻']},
{name: '刺客信条:起源', tag: ['2017', '历史']}
])
// 数组为2017,历史的文档,顺序也需要保证一致,才能正确的匹配
db.games.find({
tag: ['2017', '历史']
})
// 数组中包含‘历史’的文档
db.games.find({
tag: {
$all: ['历史']
}
})
// 复合条件查找
// 数组中一个包含2017,另一个包含历史,或者两者都包含的文档
db.games.find({
tag: {
$in: ['2017'],
$in: ['历史']
}
})
// 多条件查找
// dim_cm字段数组中至少有一个元素大于22小于30
var cursor = db.collection('inventory').find({
dim_cm: { $elemMatch: { $gt: 22, $lt: 30 } }
})
// 数组中指定元素的查找,使用点操作符,第一位为魔幻的数组
db.games.find({
'tag.1': '魔幻'
})
// 数组长度查询,长度为2的数组
db.games.find({
tag: {$size: 2}
})
数组嵌套对象的查询
db.games.insertMany([
{
time: '2018',
games: [
{
name: '战神',
month: 4
},
{
name: '最终幻想15',
month: 3
}
]
},
{
time: '2017',
games: [
{
name: '使命召唤14',
month: 11
},
{
name: '幽灵行动:荒野',
month: 3
}
]
}
])
// 需要精确匹配(字段顺序都需要精确匹配)
db.games.find({
games: {name: '最终幻想15', month: 3}
})
// 对与数组中对象的字段进行匹配
db.games.find({
'games.month': 11
})
// 指定查询的从数组的哪一位查询
db.games.find({
'games.0.month': 3
})
// 对于单一字段的查询
db.games.find({
'games.month': {
$gt: 10
}
})
// 复合条件查询
// 数组中至少有一个元素name为幽灵行动:荒野,month为3
db.games.find({
games: {
$elemMatch: {
name: '幽灵行动:荒野',
month: 3
}
}
})
文本的查询
文本搜索操作前,需要指定可以进行文本搜索的字段
db.stores.insert(
[
{ _id: 1, name: "Java Hut", description: "Coffee and cakes" },
{ _id: 2, name: "Burger Buns", description: "Gourmet hamburgers" },
{ _id: 3, name: "Coffee Shop", description: "Just coffee" },
{ _id: 4, name: "Clothes Clothes Clothes", description: "Discount clothing" },
{ _id: 5, name: "Java Shopping", description: "Indonesian goods" }
]
)
// 指定集合中的name字段,description字段可以进行文本搜索的操作
db.stores.createIndex( { name: "text", description: "text" } )
// 查询文本中包含java或Coffee
db.stores.find({
$text: {
$search: "java Coffee" // search 会以or逻辑搜索
}
})
bulkWrite
批量执行SQL语句
// 首先插入name字段为本田翼的数据,然后查询name字段为本田翼的数据,然后将name字段更新为广末凉子
db.users.bulkWrite([
{
insertOne: {
document: {
name: '本田翼'
}
}
},
{
updateOne: {
filter: { name: '本田翼' },
update: { name: '广末凉子' }
}
}
])
Mongoose
文档地址(英文), 同MongoDB一样,Mongoose已经发布了5.x版本,版本迭代的非常快,中文文档目前没有大家还是看英文文档吧
SchemaTypes
新建Schema时,为字段创建类型约束,默认值,是否必填,验证函数,等等。对于Number类型还可以设置最大最小值的约束
// 简单的示例
const schema = mongoose.Schema({
name: {
type: Number, // 类型
default: 1024, // 默认值
min: 0, // 最小值
max: 9999, // 最大值
required: true, // 是否必填
validate: function () {}, // 验证器
},
list: [Buffer] // 内容都是Buffer的数组
})
需要注意的两点:
- Schema中如果存在日期类型,直接通过Date的方法修改该字段mongoose是不会更新保存的。需要通过markModified通知mongoose发生了更新操作。
- 对于数组类型如果没有指定default,默认值为空数组
User.findOne({id: 1}, (err, user) => {
user.craateDate.setMonth(3) // mongoose是无法知晓更改的
user.markModified('craateDate') // 通知mongoose发生了更改
user.save()
})
Connections
mongoose链接到mongoDB的API
// 默认的端口号27017,本地地址127.0.0.1
mongoose.connect('mongodb://localhost/myapp');
Models
Models通过我们定义的Schema创建的,Models实例上暴露的方法,可以帮助我们保存检索数据库,这些操作都是通过Models处理的。
Models的静态方法(Model.APIname)和实例方法(Model.prototype.APIname)非常多,具体请翻阅API列表, 👇下面只是举例说明
创建Models
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const kittenSchema = new Schema({
id: String
})
const Kitten = mongoose.model('Tank', kittenSchema)
Models上的CRUD
创建文档
文档就是Models的实例,就是保存到数据库中具体的数据
// 实例方法
// model.save会将model保存到数据库
const Kitten = require('...')
let fluffy = new Kitten({ name: 'fluffy' })
fluffy.save((err, fluffy) => {
// ...
})
// 静态方法
// Modles.create会为每一个model调用save方法
const Kitten = require('...')
let list = [{name: 'jack'}, {name: 'jerry'}]
Kitten.create(list, (err, kittens) => {
// ...
})
查询文档
常用的方法有find,findById, findOne, Where
// 这几种方法的示例
const Kitten = require('...')
// 查询age大于1小于5的文档
Kitten.where('age').$get(1).$lte(5).exec((err, result) => {
// ...
})
// 查询全部数据,只需要返回name字段,从第二条数据开始查找,查找5条
Kitten.find({}, 'name', { skip: 2, limit: 5 }, (err, kittens) => {
// ...
})
// findById, 根据文档的ObjectId字段查询, ObjectId是mongoose默认添加的
Kitten.findById('5ae875c0f6ed7bd6883719b5', (err, kitten) => {
//...
})
// findOne, 查询name为fluffy的文档,但是只返回第一条数据
Kitten.findOne({name: 'fluffy'}, (err, kitten) => {
// ...
})
删除文档
常用的有Model.remove, Model.prototype.remove, Model.findByIdAndRemove
// model.remove 实例方法
Kitten.findById('5ae875c0f6ed7bd6883719b5', (err, kitten) => {
if (err) // ...
kitten.remove((err, kitten) => {
// ...
})
})
// 上面可以用findByIdAndRemove方法更好的解决
Kitten.findByIdAndRemove('5ae875c0f6ed7bd6883719b5', (err, kitten) => {
// ...
})
// Models.remove 静态方法
// 删除name为jerry的文档
Kitten.remove({name: 'jerry'}, (err, kitten) => {
// ...
})
更新文档
常用的方法有 Model.update, Model.updateOne, Model.updateMany, Model.findById AndUpdate
// 查询name为fulffy的文档,并将name字段更新为Tom
Kitten.updateMany({name: 'fluffy'}, {$set: {name: 'Tom'}}, (err, raw) => {
// ...
})
Kitten.findByIdUpdate('5ae875c0f6ed7bd6883719b5', {$set: {name: 'Tom'}}, (err, kitten) => {
// ...
})
注意:update等一系列方法,虽然可以更新文档,但是callback的第二个参数不是更新后的文档。如果想把更新后的文档返回,可以通过findByIdUpdate方法。findByIdUpdate的callback函数的第二个参数就是更新后的文档
子文档
可以理解为嵌套在文档中的文档,子文档不会单独保存,只能保存父级文档才能保存子文档
子文档的定义
const mongoose = require('mongoose')
const Schema = mongose.Schema
const fansSchema = mongoose.Schema({
name: String
})
// 子Schema嵌套在父Schema中
const starSchema = mongoose.Schema({
fans: [fansSchema]
})
const Star = mongoose.model('Star', starSchema)
创建子文档
子文档不能单独保存,必须跟随父文档才能保存
let stars = { fans: [{name: '本田翼'}, {name: '广末凉子'}] }
Star.create(stars, (err, stars) => {
// ...
})
中间件执行的顺序
子文档.pre(‘save’), 子文档.pre(‘validate’),在父文档.pre(‘save’)之前,父文档.pre(‘validate’)之后执行。
更新子文档
// 查询父文档,通过$push更新子文档
Star.findOneAndUpdate('5ae96fd7f95a0a08b7971952', {
$push: {
fans: [
{ name: '明日香' },
{ name: '绫波丽' },
]
}
}, {new: true}, (err, star) => {
// ...
})
删除子文档
与更新操作同理,首先需要找到对应的父文档,然后对子文档作出相应的操作
Star.findById('5ae9877676c318efd42dff35', (err, star) => {
if (err) // ...
// 删除子文档的数组的内容
star.fans.remove()
star.save((err, star) => {
// ...
})
})
验证
- Validation在SchemaType中定义
- 验证是异步递归的,保存父文档之前子文档也会验证,如果发生错误父文档的save将会接收到错误
- 验证可以是自定义的验证函数
- mongoose默认将验证每个save钩子
内置的验证
通过在SchemaType中定义。例如Number类型的max,min。String类型的maxlength,minlength,match(正则匹配),enum(枚举,字符串只能是枚举中的字符串)
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const goddessSchema = new Schema({
// 最小长度为2,最大长度12,必填
name: {
type: String,
minlength: 2,
maxlength: 12,
required: true
},
// 最小为16,最大为60,必填
age: {
type: Number,
min: 16,
max: 60,
required: true
}
})
unique不是验证器
unique保证了字段的value的唯一性,但其本身不是验证器
自定义验证器
自定义验证器是一个函数,返回Boolean类型的值,如果是false代表验证不通过
const goddessSchema = new Schema({
phone: {
type: String,
validate: {
// validator函数的参数是字段的value值
validator (value) {
return /^[1][3,4,5,7,8][0-9]{9}$/.test(value)
},
// 错误信息
message: '手机号码格式不正确'
},
required: true
}
})
异步验证器
异步验证器,可以接收一个Promise对象,如果对象状态为reject代表验证不通过,resolve验证通过
const goddessSchema = new Schema({
mail: {
type: String,
validate: {
validator (value) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(false)
}, 5)
})
},
message: '邮箱错误:('
}
}
})
动态验证器
Schema上暴露了一些API可以让我们动态的添加验证器
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const userSchema = new Schema({
name: {
type: Number
}
})
// 动态设置Schema中name字段的type
userSchema.path('name', String)
// 动态设置Schema中name字段的validate
userSchema.path('name').validate(function (value) {
if (value === '李航') {
return false
}
return true
})
嵌套验证器
对于嵌套对象,动态的添加验证器是非常棘手的,因为添加验证器的路径是不完整的。解决办法是通过把嵌套的对象,拆分为子文档的方式
// 错误的演示
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const gameSchema = new Schema({
info: {
name: Number,
year: Number
}
})
// Error 路径是不正确的, name字段是在info对象中
gameSchema.path('name', String)
// 正确的演示
// 解决办法把嵌套的文档拆分为子文档
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const infoSchema = new Schema({
name: Number,
year: Number
})
const gameSchema = new Schema({
info: {
type: infoSchema,
required: true
}
})
// 路径才是正确的
infoSchema.path('name', String)
const Game = mongoose.model('Game', gameSchema)
更新时的验证器⚠️⚠️⚠️
update(), findOneAndUpdate(), 操作的时候,验证器默认不进行验证的,需要通过配置runValidators为true,才会在更新的时候开启验证
为什么默认验证器是不开启的?
- 更新操作的时候,验证器内部的this指向,并不是指向最新更新的文档,所以如果验证器中有this是不适用于更新验证器的
- 更新操作的时候,更新的验证器仅在更新的key上运行(如果你只是更新name字段,只会验证name的验证器)
- 更新验证器只能在以下操作符执行(即使设置了runValidators为true,也只能在以下操作符执行)
- $set, 设置字段
- $unset, 删除字段
- $push, 为数组字段添加后续内容(只能针对数组字段)
- $each, $push的子操作符,添加内容到数组中
- $slice, $push的子操作符, 从数组第0位开始截取
- $sort, $push的子操作符, 数组排序
- $position, $push的子操作符, 从数组哪个位置插入
- $addToSet, 为数组添加内容,但是如果内容已经存在$addToSet不会做任何操作(如果是一个对象,addToSet也会判断是否重复,但是判断的条件是具有完全相同的key-value,并且字段的顺序也要一样)
- $pull, 删除数组中条件匹配的内容
- $pullAll, 删除数组中所有实例
const gameSchema = new Schema({
info: {
name: {
type: String,
validate: {
validator (value) {
if (value === '错误的') return false
return true
},
message: 'update会验证吗?'
}
},
year: {
type: Number
}
}
})
const Game = mongoose.model('Game', gameSchema)
// runValidators为false的时候,更新操作时候不会经过验证器验证,默认为false
// 可以正确的执行更新操作
Game.findByIdAndUpdate('5aea88a2c3c255038b68fdec', {
$set: {
'info.name': '错误的'
}
}, {new: true, runValidators: false}, (err, game) => {
if (err) reject(err)
resolve(game)
})
// runValidators为true的时候, 更新操作时候会强制验证
// 会抛出错误,不能正确的更新
Game.findByIdAndUpdate('5aea88a2c3c255038b68fdec', {
$set: {
'info.name': '错误的'
}
}, {new: true, runValidators: true}, (err, game) => {
if (err) reject(err)
resolve(game)
})
中间件(钩子)
😢文档还没有看完,稍后补上
Population
Mongoose封装了一个 population 的功能,当你在定义 Schema 的时候指定了某个 field 是引用了另一个 Schema ,那么你在获取 document 的时候就可以使用 populate 方法让 Mongoose 帮你通过引用 Schema 和 id 找到关联的另一个 document,并且用该 document 的内容替换掉原来引用字段的内容,使引用的 ducoment 使用起来就像是内嵌的 document 一样方便。
创建关联
- 推荐使用Schema.Types.ObjectId创建关联,对应关联的字段只会保存ObjectId(另一个表的ObjectId)
- 使用ref字段确定关联的Models
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const ownerSchema = new Schema({
name: String,
// 创建对Slave的集合,类似外键约束
// owner的集合中slaves字段只会保存slave集合的ObjectId
slaves: [{ type: Schema.Types.ObjectId, ref: 'Slave' }]
})
const slaveSchema = new Schema({
name: String
})
const Owner = mongoose.model('Owner', ownerSchema)
const Slave = mongoose.model('Slave', slaveSchema)
添加关联
const owner = new Owner({name: '久远寺森罗'})
const slave = new Slave({name: '上杉练'})
slave.save((err, s) => {
if (err) // ...
// mongoose会自动解析为objectId保存到owner的slaves数组中
// Owner集合中slaves字段将会存有slave的ObjectId作为引用
owner.slaves.push(s)
owner.save((err, h) => {
if (err) // ...
})
})
}
查询关联
mongoose会自动将ObjectId替换为对应集合中的文档。查询操作,Models,document都拥有对应的populate方法
// Query.populate
// 查询id为5aebb8a287f5ca03508b6b23的文档,并将slaves字段做populate操作,只填充name字段
Owner.findOne({_id: '5aebb8a287f5ca03508b6b23'}).populate({
path: 'slaves',
select: 'name'
}).exec((err, owner) => {
// ...
})
// document.populate
Owner.findById('5aebb8a287f5ca03508b6b23', (err, owner) => {
if (err) // ...
// owner实例做populate操作
owner.populate('slaves', (err, owner) => {
// ...
})
})
// Models.populate
Owner.findById('5aebb8a287f5ca03508b6b23', (err, owner) => {
if (err) // ...
// 对slaves字段进行population,只填充_id字段
Owner.populate(owner, {
path: 'slaves',
select: '_id'
}, (err, owner) => {
if (err) // ...
})
})
Discriminators
Discriminators是一种模型的继承机制,可以在同一个模型的基础上,创建模型。可以类比为class中继承。Discriminators拥有基础模型的字段以及中间件。
Discriminators的实现
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const schoolSchema = new Schema({
schoolName: String
}, {discriminatorKey: 'kind'})
const teacherSchema = new Schema({
name: String
})
const School = mongoose.model('School', schoolSchema)
// Teacher的模型是基于School模型创建的
// Teacher模型会拥有两个字段name以及schoolName
const Teacher = School.discriminator('Teacher', teacherSchema, {
discriminatorKey: 'kind'
})
Discriminators的保存
使用Discriminator模型创建的文档,与基础模型创建的文档都是保存在mongoDB同一个集合中
const teacher = new Teacher({
schoolName: '牡丹江师范学院',
name: '赵晨'
})
const school = new School({
schoolName: '牡丹江师范学院'
})
teacher.save((err, teacher) => {
// ...
})
school.save((err, school) => {
// ...
})
通过db.schools.find(), 可见 school和teacher都是保存在同一个集合中的
Discriminators的查询
find(), count()等方法会自动的区别不同的Discriminators
// mongoose的查询方法会自动区别不同的Discriminators
School.count({}, (err, count) => { })
Teacher.count({}, (err, count) => { })
Discriminators的中间件
Discriminators会继承基础模型的中间件,Discriminators也可以设置自己的中间件,从而不影响到基础模型
// 基于School的Discriminators也会执行save的钩子,打印出save
schoolSchema.pre('save', (next) => {
console.log('save')
next()
})
Discriminators的字段优先级
当Discriminators字段与基本模型字段相冲突的时候。Discriminators字段的优先级是大于基本模型的优先级的,但是_id除外。
const schoolSchema = new Schema({
id: String,
schoolName: String
}, {discriminatorKey: 'kind'})
const teacherSchema = new Schema({
id: Number,
name: String
})
const School = mongoose.model('School', schoolSchema)
const Teacher = School.discriminator('Teacher', teacherSchema, {
discriminatorKey: 'kind'
})
// 基础模型中id为String类型,Discriminators模型中id为Number类型,Discriminators模型的字段优先级大于基础模型
const teacher1 = new Teacher({
schoolName: '牡丹江师范学院',
name: '赵晨',
id: 1
})
const teacher2 = new Teacher({
schoolName: '南京示范学院',
name: '宋敏',
id: '1'
})
// 最后保存id将会保存为Number类型
teacher1.save((err, teacher) => console.log(typeof teacher.id === Number) // true
teacher2.save((err, teacher) => console.log(typeof teacher.id === String) // false
Discriminators与Model.create操作
mongoose会通过数据的DiscriminatorsKey字段也就是kind,保存为不同的类型
// 定义不同的Discriminators以及不同的DiscriminatorsKey
const teacherSchema = new Schema({
id: Number,
name: String
})
const studentSchema = new Schema({
id: Number,
name: String
})
const janitorSchema = new Schema({
id: Number,
name: String
})
const Teacher = School.discriminator('Teacher', teacherSchema, {
discriminatorKey: 'kind'
})
const Student = School.discriminator('Student', studentSchema, {
discriminatorKey: 'kind'
})
const Janitor = School.discriminator('Janitor', janitorSchema, {
discriminatorKey: 'kind'
})
// mongoose会根据不同kind,保存正确的类型
const lists = [
{kind: 'Teacher', id: 8, name: '明日香'},
{kind: 'Student', id: 9, name: '绫波丽'},
{kind: 'Janitor', id: 10, name: '葛城美里'}
]
School.create(lists, (err, schools) => {
// ...
})