MEAN开发栈中使用MongoDB的时候,与之配对的ORM最好的选择就是Mongoose了。本文就和大家一起探讨一下如何使用Mongoose来实现MongoDB的增删改查。

为了能使文中的例子更加生动,我们会实现一个对于用户的增删改查的RESTful API。

Mongoose简介

mongoose是一个nodejs下,专门基于no-sql数据库mongodb的ORM框架。我们可以使用mongoose轻松实现对于mongodb的操作。要是用mongoose首先要在项目中添加这个框架:

$ npm install mongoose --save

注意,这里假设你已经安装了MongoDB。如果还没有,那么请参考这里下载,参考这里安装,并创建第一个数据库。

在项目中引用mongoose:

var mongoose = require('mongoose');

连接到已有数据库:

var url = 'mongodb://localhost/yourappdatabase';
mongoose.createConnection(url);

连接到数据库后就要处理里面的数据了。

定义一个Model

在处理增删改查以前,我们先看看mongoose的Model。这些Model就代表了MongoDB库里面的Document(MongoDB术语,相当于Sql数据库的表),以后的增删改查都要通过这个Model实现。

mongoose的Schema是用来定义document的属性的。Schema中也可以定义Methods

首先定义一个model:

var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var userSchema = new Schema({
name: String,
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
admin: Boolean,
location: String,
meta: {
age: Number,
website: String
},
created_at: Date,
updated_at: Date
}); var User = mongoose.model('User', userSchema); module.exports = User;

首先使用Schema定义一个userSchema,作为之后model里包含的字段和字段的类型使用。如果schema里包含了其他的对象,那么就把这个对象定义在meta属性内。

在mongoose的Schema里给字段指定类型时可用的类型有:

  • String
  • Number
  • Date
  • Buffer
  • Boolean
  • Mixed
  • ObjectId
  • Array

之后用mongoose.model类创建Model。我们还可以在这里做更多,比如定义一个给用户密码加密的方法(稍后讲到)。

自定义方法

var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var userSchema = new Schema({
name: String,
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
admin: Boolean,
location: String,
meta: {
age: Number,
website: String
},
created_at: Date,
updated_at: Date
}); // 在Schema里添加自定义方法
userSchema.methods.capitalizeName = function () {
this.name = this.name.toUpperCase();
return this.name;
}; var User = mongoose.model('User', userSchema); module.exports = User;

使用举例

下面就看看我们自定义的model和方法如何使用:

app.post('/new', function (req, res) {
var newGuy = new User({
name: req.body.name,
username: req.body.username,
password: req.body.password // 千万别用这种密码
}); newGuy.capitalizeName(function (err, name) {
if (err) {
res.send({error: err});
return;
} console.log(`name is ${name}`);
}); newGuy.save(function (err) {
if (err) {
res.send({error: err});
} else {
res.send({message: 'done', user: newGuy});
}

这是在一个Http POST请求里获取用户的name, username, password三个值。之后使用User来创建一个新的用户newGuy,调用方法newGuy.capitalizeName,注意这个犯法里有回调。最后使用save方法保存用户数据。由于在定义User这个model的时候指定username是唯一的,所以多次插入操作只会保存一条数据用户名相同的数据。

在保存以前调用的方法

我们在model里也定义了created_at等关于时间的属性,这样就知道什么时候doc被创建。我们使用Schemapre方法来保证保存以前可以调用执行一段特定的代码。

// 保存以前执行的代码
userSchema.pre('save', function (next) {
var currentDate = new Date();
this.updated_at = currentDate; if (!this.created_at) {
this.created_at = currentDate;
} next();
});

现在每次save都会执行这段代码,给model的created_atupdated_at设定时间。这也是一个hash密码的地方。毕竟直接保存明文密码太不应该了。

Create

用前面定义好的model:User然后new一个新的变量。调用内置的save方法就会创建一个新的user。

// create a new user
var newUser = User({
name: 'Peter Quill',
username: 'starlord55',
password: 'password',
admin: true
}); // save the user
newUser.save(function(err) {
if (err) throw err; console.log('User created!');
});

读取

读取就是根据一定的条件把数据库里的user读出来。由这个条件可以决定我们是读一个,几个还是全部user。

读取全部

app.get('/all', function (req, res) {
User.find({}, function (err, users) {
if (err) {
res.send({message: 'error'});
return;
} res.send({message: 'done', data: users});
});
});

请求结果:

{
"message": "done",
"data": [
{
"_id": "576bfe34137a575cf8c854cc",
"name": "sue",
"age": 26,
"status": "A"
},
{
"_id": "576df2960f0a5baabce7bc16",
"name": "JACK",
"username": "uncle_charlie",
"password": "123456",
"__v": 0
}
]
}

查找一个

app.get('/some/:name', function (req, res) {
var name = req.params.name;
User.find({name: name.toUpperCase()}, function (err, user) {
if (err) {
res.send({message: 'error'});
return;
} res.send({message: 'done', data: user});
});
});

使用Username属性的唯一约束查找一个user。因为用户的name在数据库中是大写的,要匹配到用户需要把传过来的参数全部大写之后使用。

用ID查找

app.get('/some/:name/:id', function (req, res) {
var name = req.params.name;
var userId = req.params.id; if (name === 'none') {
User.findById(userId, function (err, user) {
if (err) {
res.send({message: 'error'});
return;
} res.send({message: 'done', data: user});
});
} else {
// 略
}
});

查询特定user

除了下面给出的方法以外,你可以可以使用MongoDB本身的查询方法

app.get('/find', function (req, res) {
var lastDay = new Date();
lastDay.setDate(lastDay.getDate() - 1); User.find({admin: false}).where('created_at').gt(lastDay).exec(function (err, users) {
if (err) {
res.send({message: 'error'});
return;
} res.send({message: 'done', data: users});
});
});

找到昨天添加的非admin用户。

更新

我们接下来找几个用户出来,修改他们的某些属性并再次存入数据库。

app.post('/update/:name', function (req, res) {
var name = req.params.name.toUpperCase();
User.find({ name: name.toUpperCase() }, function (err, user) {
if (err) {
res.send({ message: 'error' });
return;
} var aUser = user[0];
// 更改属性
// 如果是admin的全部撤销,不是admin则指定为admin
aUser.admin = req.body.admin; // 定位为Beijing
aUser.location = req.body.location; // save
aUser.save(function (err, data) {
if (err) {
res.send({ message: 'error' });
return;
} console.log(`done ${data}`);
res.send({message: 'done', data: aUser});
});
});
});

查找并更新

User.findOneAndUpdate({name: name}, {username: 'mr_charlie'}, function (err, user) {
if (err) {
res.send({ message: 'error' });
return;
} res.send({message: 'done', data: user});
});

还可以使用一个类似的方法findByIdAndUpdate,各位可以在这里查看详细的介绍。

删除

获取一个用户,然后删除。

app.get('/delete/:name', function (req, res) {
var un = req.params.name.toUpperCase();
User.find({name: un}, function (err, user) {
if (err) {
res.send({ message: 'error'});
return;
} if (user.length <= 0) {
res.send({ message: 'error', 'data': 'no such user'});
return;
} user[0].remove(function(err, data) {
if (err) {
res.send({ message: 'error' });
return;
} console.log(`data to remove ${data}`);
res.send({messge: 'done', data: data});
});
});
});

用户名会在get的路径中传入:http://localhost:4100/delete/sue。之后使用这个用户名找到用户名称为SUE的全部用户(这时是个数组),如果这个用户数组大于0,则删除数组中的第一个model。最后,把删除的用户数据通过RESTful API发送出去。

类似的方法还有:findOneAndRemovefindByIdAndRemove

// find the user with id 4
User.findOneAndRemove({ username: 'starlord55' }, function(err) {
if (err) throw err; // we have deleted the user
console.log('User deleted!');
}); // find the user with id 4
User.findByIdAndRemove(4, function(err) {
if (err) throw err; // we have deleted the user
console.log('User deleted!');
});

处理回调

前部分的内容都是关于如何使用mongoose处理数据的常用的增删改查的方法。在其中涉及到了很多的回调。比如查找之后更新这一部分,首先要查找,在回调中得知成功之后调用更新,更新回调显示成功之后把数据发回客户端。在以上步骤中任意一步出错则发送错误数据。

User.find({ name: name.toUpperCase() }, function (err, user) {
// 1. 查找以后的回调
if (err) {
res.send({ message: 'error' });
return;
} // 修改数据 // save
aUser.save(function (err, data) {
// 更新的回调
if (err) {
res.send({ message: 'error' });
return;
}
// 更新成功
console.log(`done ${data}`);
res.send({ message: 'done', data: aUser });
});
});

查找并更新的两个回调。

下面就来讲解如何消除回调。这样的神器叫做bluebird。bluebird使用了一种叫做Promise的工具,可以通过then方法依次的分解回调的代码。

安装bluebird。

npm install bluebird

在引入的mongoose里设置这个bluebird。

app.post('/asyncupdate/:name', function (req, res) {
var name = req.params.name.toUpperCase();
User.find({ name: name.toUpperCase() }).exec().then(function (users) {
if (users.length <= 0) {
throw new Error('no such user');
} var aUser = users[0];
// 更改属性
// 如果是admin的全部撤销,不是admin则指定为admin
aUser.admin = req.body.admin; // 定位为Beijing
aUser.location = req.body.location; return aUser.save();
}).then(function (user) {
console.log('updated user: ' + user.name);
res.send({message: 'done', data: user});
}).catch(function (err) {
console.log('err ' + err);
res.send({ message: 'error' });
});
});

和上面的使用回调的查找-更新比较一下,使用Promise以后代码不仅容易读,而且以后也会更加容易维护。当然,Promise能做的绝对不仅是这样写。有兴趣的同学可以到bluebird官网查看他们的文档。

最后

Mongoose是一个MongoDB下非常好用的ORM库,而且简单易学。是开发的好帮手。另外还有bluebird加成。处理mongodb的时候就更加的得心应手了。

Mongoose轻松搞定MongoDB,不要回调!的更多相关文章

  1. 跨域 - jsonp轻松搞定跨域请求

    1.jsonp轻松搞定跨域请求 vue中使用axios,遇到跨域我就蒙逼了.第一次真正意义上的尝试使用jsonp js中用 var myscript = document.createElement( ...

  2. 使用BleLib的轻松搞定Android低功耗蓝牙Ble 4.0开发具体解释

    转载请注明来源: http://blog.csdn.net/kjunchen/article/details/50909410 使用BleLib的轻松搞定Android低功耗蓝牙Ble 4.0开发具体 ...

  3. 【转】轻松搞定FTP之FlashFxp全攻略

    转载网址:http://www.newhua.com/2008/0603/39163.shtml 轻松搞定FTP之FlashFxp全攻略 导读: FlashFXP是一款功能强大的FXP/FTP软件,融 ...

  4. 轻松搞定javascript变量(闭包,预解析机制,变量在内存的分配 )

    变量:  存储数据的容器     1.声明        var   2.作用域       全局变量. 局部变量. 闭包(相对的全局变量):   3.类型         a.基本类型(undefi ...

  5. Webcast / 技术小视频制作方法——自己动手录制video轻松搞定

    Webcast / 技术小视频制作方法——自己动手录制video轻松搞定 http://blog.sina.com.cn/s/blog_67d387490100wdnh.html 最近申请加入MSP的 ...

  6. 【微服务】之二:从零开始,轻松搞定SpringCloud微服务系列--注册中心(一)

    微服务体系,有效解决项目庞大.互相依赖的问题.目前SpringCloud体系有强大的一整套针对微服务的解决方案.本文中,重点对微服务体系中的服务发现注册中心进行详细说明.本篇中的注册中心,采用Netf ...

  7. 【微服务】之三:从零开始,轻松搞定SpringCloud微服务-配置中心

    在整个微服务体系中,除了注册中心具有非常重要的意义之外,还有一个注册中心.注册中心作为管理在整个项目群的配置文件及动态参数的重要载体服务.Spring Cloud体系的子项目中,Spring Clou ...

  8. 从零开始,轻松搞定SpringCloud微服务系列

    本系列博文目录 [微服务]之一:从零开始,轻松搞定SpringCloud微服务系列–开山篇(spring boot 小demo) [微服务]之二:从零开始,轻松搞定SpringCloud微服务系列–注 ...

  9. 【微服务】之四:轻松搞定SpringCloud微服务-负载均衡Ribbon

    对于任何一个高可用高负载的系统来说,负载均衡是一个必不可少的名称.在大型分布式计算体系中,某个服务在单例的情况下,很难应对各种突发情况.因此,负载均衡是为了让系统在性能出现瓶颈或者其中一些出现状态下可 ...

随机推荐

  1. Couchbase学习和使用

    Couchbase介绍 couchbase的关键有两点:延后写入和松散存储.延后写入,顾名思义,couchbase在对数据进行增删时会先体现在内存中,而不会立刻体现在硬盘上,从内存的修改到硬盘的修改这 ...

  2. Struts2框架的数据封装一之属性封装(属性封装的第一种方式:对参数进行封装)

    request带着参数来,aciton对其进行处理.在学习action之前,使用的是servlet对request进行处理.request请求时会带有参数,所以我们要对这些参数进行封装. 1. 为什么 ...

  3. 安装nginx流程

    1.下载nginx压缩包: 下载nginx:http://nginx.org/en/download.html 本教程下载 nginx-1.14.0.zip(http://nginx.org/down ...

  4. 转:css知多少(12)——目录

    <css知多少>系列就此完结了.常来光顾的朋友可能会觉得突然:css的知识点还有很多,怎么突然就完了,还没讲完呢?这样说是对的.不过凡事都有一个定位,如果盲目求多,定位模糊,那样就没有目的 ...

  5. Java 8.9 游戏:井字游戏(C++&Java)

    思路框架同8.20 : C++: #include<iostream> #include<string> using namespace std; class Chess { ...

  6. UI设计教程:关于版式设计

    版式设计是视觉传达的重要手段之一,版式设计,即把有限的视觉元素在版面页进行有效的视觉组合,最优化地传达信息的同时,去影响受众,使受众产生视觉上的美感. 版式设计基本流程  在进行版式设计时,设计作品的 ...

  7. start()方法和run()方法有什么区别?

    通过调用线程类的start()方法来启动一个线程,使线程处于就绪状态,即可以被JVM来调度执行,在调度过程中,JVM通过调用线程类的run()方法来完成实际的业务逻辑,当run()方法结束后,此线程就 ...

  8. Why Linux Doesn’t Need Defragmenting

    If you’re a Linux user, you’ve probably heard that you don’t need to defragment your Linux file syst ...

  9. Intellij idea 系列教程目录

    Intellij idea 系列教程目录 Intellij idea 系列教程之破解方法 Intellij idea 系列教程之常用快捷键 Intellij idea 系列教程之常用配置项 每天用心记 ...

  10. 拼图类APP原型模板分享——简拼

    简拼是一款记录美好.抒写情怀的拼图APP,模板设计风格简约文艺,种类齐全. 此原型模板所用到的组件有标签组.水平分隔线.圆形工具.交互动作有结合标签组实现页面跳转,选择组件触发按钮状态变化等. 此原型 ...