用“MEAN”技术栈开发web应用(三)用mongodb搭建数据库
上一篇介绍了如何用express搭建起服务端MVC的开发架构,本篇我们来详细介绍一下这个Model层,也就是数据库访问层。包含如何使用mongodb搭建数据库,以及如何使用mongoose来访问数据。
mongodb的安装和启动
首先我们得安装mongodb,先去官网( http://www.mongodb.org/downloads)下载安装包,我的开发环境是Windows,所以下载Windows下的iso文件,根据提示一路安装完毕即可,没什么需要特别选择的。
安装完后,为了能在cmd中全局都能启动mongodb,我们需要配置环境变量,将安装目录下的bin目录配置在系统变量中,如图:
接下来,新建一个目录,用于存放数据库文件。我新建的目录为D:\mongodb\data\db,然后运行cmd,执行命令mongod --dbpath D:\mongodb\data\db,这个时候mongodb就运行起来了,我们就可以用程序来连接数据库了。
连接数据库
mongodb的默认端口是27017,我们在项目中有一个models文件夹,里面放置与数据库连接的数据模型,其中有一个mongodb.js,内容很简单:
var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/QuestionMaker'); exports.mongoose = mongoose;
是标准的commonjs模块写法,先引用mongoose模块,然后连接数据库,对外暴露mongoose模块。可以看到我们在代码中去访问本地的QuestionMaker数据库。那么首先我们得有这个数据库才行。运行cmd,创建一个数据库
use QuestionMaker
db.createCollection("counters")
此命令会创建一个名为QuestionMaker的数据库,并在该库创建一个集合counters(这个集合我们后续会用到),必须得这么写,否则数据库无法创建。驱动模块写好了,我们在其他模块中引用该模块后,就可以连接到数据库了。
使用mongoose操作数据库
我们看到上面用了一个叫做mongoose的模块,它是一个nodejs模块,mongoose提供了Schema、Model、Documents对象,用它可以更加直观的操作数据库,所以很多人喜欢这个模块,我们项目中也用它来操作数据库。
为了能以我们熟悉的面向对象的方式来访问数据库,我们先根据试题的数据结构来创建一个Schema:
var QuestionSchema = new Schema({ id: String, qtype: Number, name: String, content: String, options: [ { name: String } ], answer: String });
Schema可以理解为一个模板,根据此模版,我们可以用mongoose.model来创建出一个Question类,代码如下:
var Question = mongodb.mongoose.model("Question", QuestionSchema);
利用Question类示例化出的对象带有很多方法用来访问/操作数据库,如save方法,可以保存一条文档,也就是document(mongodb中的概念,类似我们以前概念中的一条记录)。
为了尽可能少的对外暴露数据(封装性),我新建了一个QuestionDAO对象,也就是仿造java中的概念,加一个DAO层,用来提供数据访问。对外暴露的方法都定义在这个对象上,例如保存记录和更新记录的代码如下:
//保存试题 QuestionDAO.prototype.save = function(obj, callback){ var instance = new Question(obj); instance.save(function(err){ callback(err, null); }); } //更新试题 QuestionDAO.prototype.update = function(obj, callback){ Question.findByIdAndUpdate(obj._id, obj, {}, function(err){ callback(err, null); }); }
用了mongoose提供的save方法和findByIdAndUpdate方法,每一个操作之后都可以传回调函数,这样我们就能把数据库访问的结果返回给controller层了。
另外,根据业务需求,我们就可以写更多的方法了,如项目中的list、get、remove,分别用来获取试题列表、获取试题、删除试题。其中list方法中还实现了分页返回数据。
mongodb实现id自增
id自增是数据库的一个常见需求,在mysql中,只要指定字段auto_increment即可,在mongodb中,没有这样的方法,所以必须得在代码中实现才行。我们按照官方给出的解决方法,实现questions集合的id自增。
id自增功能需要一个辅助的集合来完成,就是我们之前新建好的counters,用来记录当前的id值为多少。在mongodb命令行中执行db.counters.save({_id: "questionid", seq: 0})完成初始化。_id是mongodb默认提供的一个字段,我们这里赋值为questionid,是为了做一个标记,以便在后面能够查找出这条记录。
然后在question.js中创建CounterSchema并生成一个Counter类,用来操作counters集合,代码如下:
var CounterSchema = Schema({ _id: {type: String, required: true}, seq: { type: Number, default: 1 } }); var Counter = mongodb.mongoose.model("Counter", CounterSchema);
然后,在QuestionSchema的save方法之前,我们应该先去counters中获取最新id,可以用pre方法来实现,代码如下:
QuestionSchema.pre('save', function(next) { var doc = this; Counter.findByIdAndUpdate({_id: 'questionid'}, {$inc: { seq: 1} }, function(error, counter) { if(error) return next(error); doc.id = counter.seq; next(); }); }); QuestionSchema.set('toObject', { getters: true });
如此一来,每次Question执行save方法的时候,都会调用这个函数,从而获取到递增后的最新id。
备份和导入
数据库的备份和导入当然是必不可少了,比如大家可以拿这我的备份文件直接导入来练习,这样就省去了一步一步创建的步骤。
备份数据库可以使用mongodump命令,用法如下:
mongodump -h dbhost -d dbname -o dbdirectory
导入已有的数据库可以使用mongostore命令,用法如下:
mongorestore -h dbhost -d dbname –directoryperdb dbdirectory
具体的参数可以在网上搜索,文章很多,此处不再详细说明。
客户端工具
mongodb有没有像mysql那样的客户端工具呢?当然有啦,我使用的是robomongo这个工具,简单好用,界面如下,大家可以尝试使用。
使用MEAN技术栈开发web应用系列三部曲就到这里了,再次附上练手项目的地址:https://github.com/Double-Lv/QuestionMaker
另外,本系列文章只是一个入门级别的介绍,所写的代码以及数据库的设计基本上都属于”玩具代码“,写一个自己玩的项目还行。在实际的项目中,我们还得考虑各种因素,性能、负载等等,更多话题有待研究。