2017-09-10
DB
Mongo
使用 Mongoose 的各种好处
原生自然有原生的好处,但是,Mongo 自身某些特性不太适合开发,比如没有统一的 来约束 documents, 这会造成很多问题。
Mongoose 在原生的基础上进行了拓展,很大程度上弥补了这些不足。
本篇我将在某个名为 db 的文件夹完成如下模块来完成对数据库的抽象:
  1. db-config.js
  2. connect.js
  3. schemas.js
  4. models.js
  5. index.js

# db-config.js

这是个配置文件:
00// db-config.js
01module.exports = {
02    mongoUri: 'mongodb://127.0.0.1/nc'
03}
待会会用到。

# connect.js

该文件主要完成利用 mongoose 连接到数据库并取得一个数据库连接。
00// connect.js
01const mongoose = require('mongoose')
02    , { mongoUri } = require('./db-config')
03
04// Connect 
05mongoose.connect(mongoUri); 
06
07module.exports = mongoose;

# schemas.js

Mongoose 里的 Schema 即模式,描述了一个 Collection 的结构,是整个 Mongoose 大厦的基石。
schemas.js 里写了各种 Collection 的 schema,如果你对所谓 模式 还不是很清楚,我想你只要看了如下代码,就有一个莫名的认知了:
00// schemas.js
01const mongoose = require('./connect'); 
02
03let schemas = {}; 
04
05schemas.user = mongoose.Schema({
06    name: {
07        type: String, 
08        required: true
09    },
10    avatar: {
11        type: String, 
12        required: true
13    },
14    bg: {
15        type: String,
16        default: 'bg no set'
17    }, 
18    created_at: {
19        type: Date, 
20        default: Date.now
21    }
22});
23
24module.exports = schemas;
上面可以看到 schemes.user 是一个 schema,上面有 name avatar bg created_at 这些,实际上这些就是 schemes.user 的一个实例应该具备的 字段
也就是一个 document 里应该有的字段, Mongo 原生很宽松,什么东西都可以往 Collection 里填,而实际情况下,并不是什么东西都可以扔进里面的,尤其是处理用户 POST 过来的表单时更为重要,万一有人不怀好心发各种数据污染数据库就很让人不爽了

为此,就要在 HTTP 路由里面写上很多校验值是否合法的逻辑,这样反而增加了好多有的没的工作量 …
关于数据的合法性问题我还写过两篇博客讨论这个问题,学习了 Mongoose 之后,我发现自己造的轮子远没有 Mongoose 的类型检查优雅(虽然想法很近似233 都是有一个 schemas 来描述数据格式)。
关于如何使用 Mongoose 的类型检查,将会在文末讲述,现在这部分是完成 db 包.

# models.js

MongoDB 的结构如此简单以至于人人都能理解:
  1. 一个 MongoDB 数据库里面有很多 Collections
  2. 一个 Collection 里面有很多 Documents
schema 描述了一个 Collection 里所有 Documents 应该遵从的法则,而 Model 实际上就是这个 Collection 、及其上面所有运算的一个系统 …
这不是代数系统吗?!!
00// models.js 
01// Schemas Map To Models 
02const schemas = require('./schemas')
03    , mongoose = require('./connect'); 
04
05let schemasNames = Object.keys(schemas); 
06
07let models = schemasNames.reduce((acc, name) => {
08    acc[name + 'Model'] = mongoose.model(
09        name,
10        schemas[name]
11    );
12
13    return acc;  
14}, {})
15
16module.exports = models;
经过上述代码处理后 models 里面是这样:
00{
01    userModel: 一个由schemas.user得到的model 
02}

# index.js

index.js 什么事也没做 仅仅是 models.js 的代理
00// index.js
01const models = require('./models'); 
02
03module.exports = models;
至此我们的 db 包完成了。 可以如下方式引用:
00const db = require('./db');

# 查询

00const { userModel } = require('./db'); 
01
02userModel.find().then(docs => {
03    // docs 是一个数组,即 documents 代表数据实例 
04    console.log(docs); 
05}, err => {
06    // 查询错误 
07    console.log(err); 
08});

# 插入

Mongoose 的插入极有意思,利用 model 来完成数据实例化:
先来看看关于 user 的 schema :
00name: {
01    type: String, 
02    required: true
03},
04avatar: {
05    type: String, 
06    required: true
07},
08bg: {
09    type: String,
10    default: 'bg no set'
11}, 
12created_at: {
13    type: Date, 
14    default: Date.now
15}
  1. required 表示是否必填
  2. type 表示类型
  3. default 表示默认值,可以是函数,需要时候会被调用 比如 created_at
除此之外还有很多这些字段,都可以在官方文档上找到。
回到正事,插入一个数据:
00const { userModel } = require('./db'); 
01
02let data = new userModel({
03    name: 'varEczn', 
04    avatar: 'xxxx.jpg'
05}); 
06
07data.save().then(suc => {
08    console.log('保存成功'); 
09}, err => {
10    console.log('失败'); 
11})
如果数据校验出错,则 save() 返回的 Promise 将会转为 Rejected 而不是 Resolved 而 err 里面将会写上错误的原因,包括类型校验的错误。

# 更新

更新也很有意思:
00let id = 'xxx'; 
01userModel.findOne({
02    id: id
03}).then(doc => {
04    if (doc){
05        doc.set({
06            name: '新名字'
07        }); 
08
09        doc.save().then(suc => {
10            console.log('更新成功')
11        }, err => {
12            console.log('校验错误'); 
13        })
14    } else {
15        console.log(`${id} Not Found`)
16    }
17})

# 删除

或许你也猜到了,的确如此:
00let id = 'xxx'; 
01userModel.remove({
02    id: 'xxx'
03}).then(db => {
04    console.log('删除成功:', db.result); 
05}, err => {
06    console.log('删除失败,原因:', err); 
07});

# More

利用 Schema 可以构造出 Model, Model 是对原生查询里面 Colletion 的一个抽象,它看起来很像一个代数系统。
使用的时候也非常舒服:
00model.find({
01    id: 'xxx'
02}).then(docs => {
03    // do sth 
04})
其他的操作也都支持 promise 用起来很爽。
此外,Mongoose 封装了一些很好的方法 比如 FindOneAndRemoveFindOneAndUpdate 解决了好多痛点




回到顶部