简介:
- 传统关系类型(ORM:Object-Relational Mapper),MongoDB(ODM:Object Document Mapper);
- MongoDB是一个面向文档,schme无关(可以将任意类型的文档数据存储到集合中)的数据库;
- MongoDB中可以将数据都看作文档,文档可以是任意深度的;
- 当有数据存储后,这些文档会以十分接近JSON格式的形式存储;
- 文档数据的数据类型可以是混合的,Node.js获取储存文档数据后,其类型和储存时候的类型是一样的;
例子:
- package.json:
{ "name": "user-auth-example", "version": "0.0.1", "dependencies": { "express": "2.5.8", "mongodb": "1.3.19", "jade": "0.20.3" } }
使用jade: //缩进,默认两个空格 ; //注意tab和space不要混着用
- views/layout.jade:
doctype 5 html head title MongoDB example body h1 My first MongoDB app hr block body
- views/index.jade:
extends layout block body if (authenticated) p Welcome back, #{me.email} a(href="/logout") Logout else p Welcome new visitor! ul li: a(href="/login") Login li: a(href="/signup") Signup
- views/login.jade:
extends layout block body form(action="/login", method="POST") fieldset legend Log in if (signupEmail) #{signupEmail} p Congratulations on signing up! Please login below p label Email input(name="user[email]", type="text", value=signupEmail) p label Password input(name="user[password]", type="password") p button submit p a(href="/") Go back
- views/signup.jade:
extends layout block body form(action="/signup", method="POST") fieldset legend Sign up p label First input(name="user[first]", type="text") p label Last input(name="user[last]", type="text") p label Email input(name="user[email]", type="text") p label Password input(name="user[password]", type="password") p button Submit p a(href="/") Go back
- server.js:
var express = require('express'); var mongodb = require('mongodb'); var ObjectId = mongodb.ObjectID; var app = express.createServer(); //加入中间件 app.use(express.bodyParser()); app.use(express.cookieParser()); app.use(express.session({secret: 'my secret'})); app.use(function(req, res, next) { //身份验证中间件 if (req.session.loggedIn) { res.local('authenticated', true); //express 的res.local API app.users.findOne({"_id": ObjectId(req.session.loggedIn)}, function(err, doc) { if (err) return next(err); res.local('me', doc); next() }) } else { res.local('authenticated', false); next() } }); app.set('view engine', 'jade'); //express 3不需要这段代码 app.set('view options', {layout: false}); //创建mongodb.Server 初始化服务器 var server = new mongodb.Server('127.0.0.1', 27017); //告诉驱动器连接数据库'my-website',不存在会创建 new mongodb.Db('my-website', server, {w: 1}).open(function(err, client) { if (err) { throw err } console.log(' 33[96m + 33[39m connected to mongodb'); //创建数据库操作集合的快捷方式 app.users = new mongodb.Collection(client, 'users'); // 不管索引是否存在,都可以调用这个命令来确保在查询前建立了索引 client.ensureIndex('users', 'email', function(err) { if (err) throw err client.ensureIndex('users', 'password', function() { if (err) throw err }); console.log(' 33[96m + 33[39m ensured indexes'); app.listen(3000, function() { console.log(' 33[96m + 33[39m app listening on *:3000'); }) }) }); app.get('/', function(req, res) { res.render('index') }); app.get('/login', function(req, res) { if (req.session.loggedIn) { res.redirect('/'); } else { res.render('login', {signupEmail: ''}); } }); app.get('/login/:signupEmail', function(req, res) { res.render('login', {signupEmail: req.params.signupEmail}); }); app.post('/login', function(req, res) { //文档查找 app.users.findOne({email: req.body.user.email, password: req.body.user.password}, function(err, doc) { if (err) return next(err) if (!doc) return res.send('User not found. Go back and try again'); req.session.loggedIn = doc._id.toString(); res.redirect('/'); }) }); app.get('/logout', function(req, res) { req.session.loggedIn = null res.redirect('/'); }); app.get('/signup', function(req, res) { res.render('signup'); }); app.post('/signup', function(req, res, next) { //创建文档 app.users.insert(req.body.user, function(err, doc) { if (err) { return next(err) } res.redirect('/login/' + doc[0].email); }) });
问题:
- 校验://表单校验类型,大小等; mongoose通过在应用层定义scheme(模型)来解决;
- 原子性://多个用户同时对同一文档进行了操作; mongoose通过检查要对文档做的修改,并只修改受影响的字段来解决;
- 安全模式:在使用驱动时,对文档的操作中有一个可选参数: app.users.insert({},{<options>});其中safe选项对在对数据库进行修改时启动安全模式;默认情况下,在操作完成后,如果有错误发生,MongoDB不会及时通知;驱动器需要早操作后调用db.getLastError,来验证数据修改是否成功; mongoose默认会对所有操作启用安全模式;
Mongoose://mongoose假定绝大部分应用程序都是用一个数据库;使用mongoose的时候无需关系连接是否建立,它会把数据库操作指令缓存起来,在连上数据库后发送出去;
- 定义模型:类型包括Date,String,Number,Array,Object,ObjectId(MongiDB提供);例子:
var Schema = mongoose.Schema; var ObjectId = Schema.ObjectId; //创建模型 var PostSchema = new Schema({ author : ObjectId, title: {type : String, default: 'default' }, //选项格式 body : String, date : Date }); //注册模型 var Post = mongoose.model("BlogPost', PostSchema); //获取模型 var Post = mongoose.modek('BlogPost'); //操作模型:创建 new Post({title: “My Title"}).save(function (err) { .... });
- 定义嵌套的键:
var BlogPost = new Schema({ title : String, meta : { votes : Number, favs : Number } }); //特定查找 db.blogposts.find({'meta.votes':5});
- 定义嵌套的文档:
var Comments = new Schema({......}); var BlogPost = new Schema({ ..... comments : [Comments], ..... });
- 构建索引:要对指定的键做索引,需要传递一个index选项,并将值设置为true;
//设置title键做索引,并将uid键设置为唯一; var BlogPost = new Schema({ author : ObjectId, title : {type: String, index : true}, uid : {type : Number, unique : true} })
- 中间件:有时候会在不同的地方以不同的方式对同样的数据进行修改,通过模型接口对这部分对数据库的交互集中避免代码重复;
//定义一操作在特定动作前执行,如在删除博文时发送电子邮件给作者; Blogpost.prev('remove', function (next) { emailAuthor(this.email, 'Blog post removed!'); next(); });
- 探测模型状态:根据要对当前模型做的不同更改进行不同操作
Blogpost.prev('save', function(next) { if(this.isNew) { .... } else { ..... } });
- 查询:
- find
- findOne
- findById //根据ObjectId
- remove
- update
- count
- 扩展查询:如果对某个查询不提供回调函数,那么直到调用run才会执行;
Post.find({autor: '...'}) .where('title','My title') .sort('content', -1) .limit(5) .run(function(err, post) { ............... })
- 排序:
query.sort('key', 1); query.sort('some.key',-1);
- 选择:若文档大,只想要指定部分;
Post.find().select('field', 'field2');
- 限制:现在查询结果的数量;
query.limit(5);
- 跳过:跳过指定数量的文档数据
query.skip(10);
- 自动产生键:
//查询一个博文的时候,还获取对应的作者;为ObjectId类型提供一个href属性; var BlogPost = new Schema({ author : {type : ObjectId, ref : 'Author'}, title : String, body : String, meta : { votes : Number, favs : Number } }) //对指定键调用populate就可自动生成; BlogPost.find({title : 'My title'}) .populate('author') .run(function (err, doc) { console.log(doc.ahthor.email); });
- 对上一个server.js使用mongoose之后:
var express = require('express'); var mongoose = require('mongoose'); //建立模型 var Schema = mongoose.Schema; var User = mongoose.model('User', new Schema({ first: String, last: String, email: {type: String, unique: true}, password: {type: String, index: true} })); var app = express.createServer( express.bodyParser(),express.cookieParser(),express.session({secret: 'my secret'}) ); app.use(function(req, res, next) { if (req.session.loggedIn) { res.local('authenticated', true); User.findById(req.session.loggedIn, function(err, doc) { if (err) return next(err); res.local('me', doc); next(); }); } else { res.local('authenticated', false); next(); } }); app.set('view engine', 'jade'); app.set('view options', {layout: false}); app.get('/', function(req, res) { res.render('index') }); app.get('/login', function(req, res) { if (req.session.loggedIn) { res.redirect('/'); } else { res.render('login', {signupEmail: ''}); } }); app.get('/login/:signupEmail', function(req, res) { res.render('login', {signupEmail: req.params.signupEmail}); }); app.post('/login', function(req, res) { //文档查找 User.findOne({email: req.body.user.email, password: req.body.user.password}, function(err, doc) { if (err) return next(err) if (!doc) return res.send('User not found. Go back and try again'); req.session.loggedIn = doc._id.toString(); res.redirect('/'); }) }); app.get('/logout', function(req, res) { req.session.loggedIn = null res.redirect('/') }); app.get('/signup', function(req, res) { res.render('signup') }); app.post('/signup', function(req, res, next) { var user = new User(req.body.user); user.save(function(err) { if (err) return next(err); res.redirect('/login/' + user.email); }); }); //连接数据库 mongoose.connect('mongodb://127.0.0.1/my-website'); app.listen(3000, function() { console.log(' 33[96m + 33[39m app listening on *: 3000'); });