Mongoose基础操作入门

Mongoose

Mongoose 是在 node 环境下对 MongoDB 进行便捷操作的对象模型工具。

创建数据库连接

利用 Mongoose 的 connect 创建数据库连接。

mongoose.connect(url, options)

1
2
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/blog');

你可以监听连接数据库的各种事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var mongoose = require('mongoose');
var DB_URL = 'mongodb://localhost:27017/blog';
mongoose.connect(DB_URL);

// 连接成功
mongoose.connection.on('connected', function () {
console.log('Mongoose connection open to ' + DB_URL);
});

// 连接异常
mongoose.connection.on('error',function (err) {
console.log('Mongoose connection error: ' + err);
});

// 连接断开
mongoose.connection.on('disconnected', function () {
console.log('Mongoose connection disconnected');
});

// 除了上面这种方式监听,还可以通过回调函数方式

mongoose.connect('mongodb://localhost:27017/blog', function(err) {
if (err) {
console.log('Mongoose connection error: ' + err);
}
else {
console.log('Mongoose connection is successful');
}
});

指定用户连接:

1
mongoose.connect('mongodb://username:password@127.0.0.1:27017/database');

连接多个数据库:

1
2
3
mongoose.connect('urlA, urlB, ...', {
mongos: true
})

关闭数据库连接

1
mongoose.disconnect()

Schema

Schema 是一种以文件形式存储的数据库模型骨架。Schema 主要用于定义 MongoDB 中集合 Collection 里文档 Document 的结构(不仅能够定义文档的结构和属性,和可以定义文档的实例方法、静态模型方法、复合索引等),每个 Schema 会映射到 MongoDB 中的一个 Collection,它不具备数据库的操作能力。

1
2
3
4
5
6
7
var Schema = mongoose.Schema;
var UserSchema = new Schema({
username: String,
userpwd: String,
age: Number,
sex: String
})

Schema 中字段的类型,可取值为:

  • String 字符串
  • Number 数字
  • Date 日期
  • Buffer 二进制
  • Boolean 布尔值
  • Mixed 混合类型
  • ObjectId 对象ID
  • Array 数组

Model

Model 是由 Schema 生成的的模型,可以对数据库进行操作。Model 的每一个实例,就是一个 Document ,Document 可以保存到数据库和对数据库进行操作。

1
var UserModel = mongoose.model('users', UserSchema);

实例化文档及保存

1
2
3
4
5
6
7
8
9
10
11
12
13
var userDoc = new UserModel({
username: 'node',
userpwd: '123456',
age: 8,
sex: 'male'
})

userDoc.save(function(err, doc) {
if (err) {
// 错误处理代码
}
console.log(doc);
})

常用数据库操作

对于数据库操作而言,平时最为平常莫非 CRUD 了。

文档新增

  • save()
  • 使用模型的create()
  • 使用模型的insertMany()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// save是document的方法
var userDoc = new UserModel({
username: 'node',
userpwd: '123456',
age: 8,
sex: 'male'
})

userDoc.save(function(err, doc) {
if (err) {
// 错误处理代码
}
console.log(doc);
})

// create方法是直接在 Model 上进行操作的,并且可以同时新增多个文档。create({},{}, function(err, doc1, doc2){})

UserModel.create({
username: 'node',
userpwd: '123456',
age: 8,
sex: 'male'
}, {
username: 'vue',
userpwd: '123456',
age: 3,
sex: 'male'
}, function(err, doc1, doc2) {
if (err) {
// 异常处理
}
console.log(doc1);
console.log(doc2);
})

// insertMany([], function(err,docs){})
UserModel.insertMany([{
username: 'node',
userpwd: '123456',
age: 8,
sex: 'male'
}, {
username: 'vue',
userpwd: '123456',
age: 3,
sex: 'male'
}], function(err, docs) {
if (err) {
// 异常处理
}
console.log(docs);
})

文档查询

Mongoose 提供了三种方法来查询文档:

  • find()
  • findeById()
  • findOne()
find

Model.find(condtions, [fields], [options], [callback])

  • conditions查询条件
  • [fields]查询字段
  • [options]查询配置参数
  • [callback]回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 查询所有数据
UserModel.find(function(err, docs) {
if (err) {
console.log(err);
// 异常处理,下同
}
console.log(docs);
})

// 查找年龄大于等于18的所有数据
UserModel.find({age: {$gte: 18}}, function(err, docs) {
if (err) {
console.log(err);
//
}
console.log(docs);
})

// 查找年龄大于等于18,且名字中包含 node 的数据
UserModel.find({age: {$gte: 18}, name: /node/}, function(err, docs) {
...
})

// 查找名字包含 node, 且只输出 name 字段
UserModel.find({name: /node/}, 'name', function(err, docs) {
...
})
// 查找所有数据,并只输出包含 name , sex 字段的数据
UserModel.find(null, {name: 1, sex: 1, _id: 0}, function(err, docs) {
...
})
// 查找跳过前两条的所有数据,如果要使用第三个条件,前两个参数无值,需设置为null
UserModel.find(null, null, {skip: 2}, function(err, docs) {
...
})
findById

Model.findById(id, [fields], [options], [callback])

  • id
  • [fields]查询字段
  • [options]查询配置参数
  • [callback]回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 显示第O个元素所有字段
var result = [];
UserModel.find(function(err, docs) {
docs.forEach(function(item, index, arr) {
result.push(item._id);
})
UserModel.findById(result[0], function(err, doc) {
...
})
})

// 上面写法也可以换做下面这种写法
var result = [];
UserModel.find(function(err, docs) {
docs.forEach(function(item, index, arr) {
result.push(item._id);
})
UserModel.findById(result[0]).exec(function(err, doc) {
....
})
})
// 只输出 name字段
UserModel.findById(result[0],{name: 1, _id: 0}).exec(function(err, doc) {
....
})
// 输出最少字段
UserModel.findById(result[0],{lean: true}).exec(function(err, doc) {
....
})

findOne

该方法找出符合条件的所有实例的第一个。

Model.findOne(condtions, [fields], [options], [callback])

  • conditions查询条件
  • [fields]查询字段
  • [options]查询配置参数
  • [callback]回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 找出 age 大于20的文档中的第一个
UserModel.findOne({age: {$gt: 20}}, function(err, doc) {
...
})
UserModel.findOne({age: {$gt: 20}}).exec(function(err ,doc) {
...
})

// 找出 age 大于20的文档中的第一个,且只输出name 字段
UserModel.findOne({age: {$gt: 20}}, {name: 1, _id: 0}, function(err, doc) {
...
})
UserModel.findOne({age: {$gt: 20}}, {name: 1, _id: 0}).exec(function(err, doc) {
...
})

// 找出 age 大于20的文档中第一个,且输出包含 name 字段在内的最短字段
UserModel.findOne({age: {$gt: 20}}, "name", {lean: true}, function(err, doc) {
...
})
UserModel.findOne({age: {$gt; 20}}, "name", {lean: true}).exec(function(err, doc){
...
})
$where

如需更复杂的查询,可以使用$where 操作符。$where 操作符可以使用javascript表达式字符串或者javascript函数。

  • 使用字符串
    1
    2
    3
    4
    5
    6
    Model.find({$where: 'this.x === this.y'}, function(err, docs){
    ...
    })
    Model.find({$where: 'obj.x === obj.y'}, function(err, docs){
    ...
    })
  • 使用函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Model.find({$where: function() {
    return obj.x !== obj.y;
    }}, function(err, docs} {
    ...
    });
    Model.find({$where: function() {
    return this.x !== this.y;
    }}, function(err, docs) {
    ...
    })

文档更新

  • update
  • updateMany
  • find() + save()
  • updateOne()
  • finedOne() + save()
  • findByIdUpdate()
  • findOneAndUpdate()
update

Model.update(condtions, update, [options], [callback])

options选项如下:

  • safe(boolean) 默认为 true, 安全模式
  • upsert(boolean) 默认为false,如果文档不存在,是否创建
  • multi(boolean) 默认为false,是否更新多个查询记录
  • runValidators 如果为true,执行 Validation 验证
  • setDefaultsOnInsert 如果upsert选项为true, 在新建时插入文档定义的默认值
  • strict(boolean) 以strict模式进行更新
  • overwrite(boolean) 默认为false, 禁用update-only模式,允许覆盖文档。
1
2
3
4
5
6
7
8
9
// 只会更改查询到的第一组数据的性别
UserModel.update({"sex": "female"}, {"sex": "male"}, function(err, res) {
...
})

// 更新多个记录
UserModel.update({'sex': 'female'}, {'sex': 'male'}, {multi: true}, function(err, res) {
...
})
updateMany

updateMany 与 update 方法唯一区别是默认更新多个文档,即使设置 {multi: ‘false’} 也无法只更新第一个文档。

Model.updateMany(conditions, update, [options], [callback])

1
2
3
4
// 将数据库中 name 字段包含 xiao 的文档, sex 变为 female
UserModel.updateMany({name: /xiao/}, {'sex': 'female'}, function(err, res) {
...
})
find() + save()
1
2
3
4
5
6
7
8
9
UserModel.find({name: /xiao/}, function(err, docs) {
if (err) {
console.log(err);
}
docs.forEach(function(item, index, arr) {
item.sex = 'male';
item.save();
})
})
updateOne

updateOne 只能更新找到的第一个文档,即使设置 {multi: true} 也无法更新多个文档。

1
2
3
UserModel.updateOne({name: /xiao/}, {'sex': 'female'}, function(err, res) {
...
})
findOne() + save()
1
2
3
4
UserModel.findOne({name: /xiao/}, function(err, doc) {
...
doc.save();
})
findOneAndUpdate

注意回调函数的参数

1
2
3
UserModel.findOneAndUpdate({name: /xiao/}, function(err, doc) {
...
})
findByIdAndUpdate

注意回调函数的参数

1
2
3
UserModel.findByIdAndUpdate('5a73194a2fd8c96fc87657aa', function(err, doc){
...
})

文档删除

  • remove()
  • findOneAndRemove()
  • findByIdAndRemove()
remove

remove 有两种形式,一种是 Model 的 remove 方法,一种是 Document 的 remove 方法。
remove 方法的回调函数不能省略,否则会无法删除数据。

1
2
3
4
5
6
7
UserModel.remove({name:/xiao/}, function(err) {
...
})

document.remove(function(err) {
...
})
findOneAndRemove

Model.findOneAndRemove(conditions, [options], [callback])

1
2
3
UserModel.findOneAndRemove({sex: 'female'}, function(err ,doc) {
...
})
findByIdAndRemove

Model.findByIdAndRemove(id, [options], [callback])

关于自定义方法

Model 实例是 Document,实例内置的方法有很多,比如 save。

通过 Schema 对象的 methods 属性给实例自定义扩展方法

通过 Schema 对象的 statics 属性给 Model 添加静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var schema = new mongoose.Schema({num: Number, name: String, age: Number});
schema.methods.findSimilarAges = function(cb) {
return this.model('MyModel').find({age: this.age}, cb);
}
schema.statics.findByName = function(name, cb) {
return this.find({age: new RegExp(name, 'i')}, cb);
}
var MyModel = mongoose.model('MyModel', schema);
var doc1 = new MyModel({num: 0, name: 'node', age: 8});
var doc2 = new MyModel({num: 1, name: 'vue', age: 8});
var doc3 = new MyModel({num: 2, name: 'react', age: 8});
doc1.save();
doc2.save();
doc3.save();
setTimeout(function() {
doc1.findSimilarAges(function(err, docs) {
docs.forEach(function(item, index, arr) {
...
})
})
}, 0)
setTiemout(function() {
MyModel.findByAge('node', function(err, docs) {
...
})
})

前后钩子

前后钩子即 pre() 和 post() 方法,又称中间件,实在执行某些操作的时候,可以执行的函数。中间件在 Schema 上指定,类似于静态方法和实例方法。

可以在数据库执行下列操作时,设置前后钩子

  • init
  • validate
  • save
  • remove
  • count
  • find
  • findOne
  • findOneAndRemove
  • fineOneAndUpdate
  • insertMany
  • update

在执行上述操作之前,执行 pre 方法。
在执行上述操作之后,执行 post 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var schema = new mongoose.Schema({ age: Number, name: String, x: Number, y: Number});
schema.pre('find', function(next) {
console.log('pre');
next();
})
schema.post('find', function(next) {
console.log('post');
next();
})

var temp = new mongoose.model('temp', schema);

temp.find(function(err, docs) {
console.log('find');
})

// pre
// find
// post

查询后处理

常用查询后处理的方法如下:

  • sort 排序
  • skip 跳过
  • limit 限制
  • select 显示字段
  • exect 执行
  • count 计数
  • distinct 去重
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// sort 
UserModel.find().sort('age').exec(function(err, docs) {
console.log(docs)
})
// 按 x 从小到大, age 从大到小排列
UserModel.find().sort('x -age').exec(function(err, docs) {
...
})


// skip
// 跳过1个,显示其他
UserModel.find().skip(1).exec(function(err, docs) {
...
})

// limit
// 显示10条数据
UserModel.find().limit(10).exec(function(err, docs) {
...
})

// select
// 显示 nage age 字段,不显示 _id 字段
UserModel.find().select('name age -_id').exec(function(err, docs) {
...
})
UserModel.find().select({name: 1, age: 1, _id: 0}).exec(function(err, docs) {
...
})

// count 显示集合中文档数量
UserModel.find().count(function(err, count) {
...
})
// distinct
UserModel.find().distinct('age', function(err, distinct) {
...
})

文档验证

为什么要进行文档验证呢,以下面例子进行说明, Schema 进行如下定义:

1
var schema = new mongoose.Schema({ age: Number, name: String, x: Number, y: Number});

如果不进行文档验证,保存文档时,就可以不按照 Schema 设置的字段进行设置,分下面几种情况:

  • 缺少字段的文档可以保存成功
    1
    2
    3
    4
    5
    var UserModel = mongoose.model('user', schema);
    new UserModel({age: 0}).save(function(err, doc) {
    ...
    })

  • 包含未设置字段的文档可以保存成功,未设置字段不被保存
    1
    2
    3
    new UserModel({age: 10, abc: 'abc'}).save(function(err, doc) {
    ...
    })
  • 包含字段类型与设置不同字段的文档也可以保存成功,不同字段类型的字段被保存为设置的字段类型
    1
    2
    3
    4
    new UserModel({age: true, name: 10}).save(function(err, doc) {
    //{age: 1, name: '10', _id: 1341234aff2f...}
    ....
    })
    通过文档验证,可以避免以上几种发生。

{name: {type: Stirng, validator: value}}

常用验证有以下几种:

  • required: 必填
  • default: 默认值
  • validate: 自定义匹配
  • min: 最小值,只适用数字
  • max: 最大值,只适用数字
  • match: 正则匹配,只适用字符串
  • enum: 枚举匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// required
// 将 age 设置为必填字段,如果没有 age 字段,文档将不被保存,且出现错误提示
var schemaa = new mongoose.Schema({age: {type: Number, required: true}, name: String, x: Number, y: Number});
var UserModel = mongoose.model('user', schema);
new UserModel({name: 'abc'}).save(function(err, doc) {
// message: 'Path `age` is required.
console.log(err);
})

// default
// 设置 age 字段的默认值为18,如果不设置 age 字段,则会取默认值
var schema = new mongoose.Schema({age: {type: Number, default: 18}, name: String, x: Number, y: Number});
var UserModel = mongoose.model('user', schema);
new UserModel({name: 'node'}).save(function(err, doc) {
// {name: 'node', age: 18, _id: ....}
...
})

// min/max
// 将 age 的取值范围设置为[0, 10],如果将 age 取值为20,文档将不会被保存,且出现错误提示
var schema = new mongoose.Schema({age: {type: Number, min: 0, max: 10}, name: String, x: Number, y: Number});
var UserModel = mongoose.model('user', schema);
new UserModel({age: 20}).save(function(err, doc) {
// Path 'age' (20) is more than maximum allowed value (10)
...
})

// match
// 将 name 的 match 设置为必须包含 a 字段,如果 name 不存在 a ,文档将不会被保存,且出现错误提示
var schema = new mongoose.Schema({ age: Nuber, name: {type: String, match: /a/}, x: Number, y: Number});
var UserModel = mongoose.model('user', schema);
new UserModel({name: 'bbb'}).save(function(err, doc) {
// Path 'name' is invalid (bbb)
...
}

// enum
// 将 name 枚举值设为['a', 'b', 'c'], 如果 name 不在枚举范围内取值,文档将不会保存,且出现错误提示
var schema = new mongoose.Schema({ age: Number, name: {type: String, enum: ['a', 'b', 'c']}, x: Number, y: Number});
var UserModel - mongoose.model('user', schema);
new UserModel({ name: 'bbb'}).save(function(err, doc) {
// 'bbb' is not a valid enum value for path 'name'
})

// validate
// validate 实际上是一个函数,函数的参数代表当前字段,返回 true 表示通过验证,返回false表示未通过验证。利用validate可以自定义任何条件,比如,定义名字 name 长度必须在4个字符以上。
var validateLength = function(arg) {
if (arg.length > 4) {
return true;
}
return false;
}
var schema = new mongoose.Schema({name: {type: String, validate: validateLenth}, age: Number, x: Number, y: Number});
var UserModel = mongoose.model('user', schema);
new UserModel({name: 'abc'}).save(function(err, doc) {
// validator failed for path 'name' width value 'abc'
...
})

参考