MEAN Web 开发 #7:MongoDB 和 Mongoose





5.00/5 (4投票s)
MEAN Web 开发系列文章的第七篇。
上周我们了解了 AngularJS 的一些基本功能,至少足以让你上手。在此之前,我们已经学习了 Node.js 和 Express。所以 EAN 已经有了,现在还剩下 M。好吧,Dial M for MongoDB,因为这正是我们这周要学习的内容。
- MEAN Web 开发 #1:MEAN 是什么以及为什么使用它
- MEAN Web 开发 #2:Node.js 在后端
- MEAN Web 开发 #3:更多 Node.js
- MEAN Web 开发 #4:Node.js Express 全面起航!
- MEAN Web 开发 #5:Jade 和 Express
- MEAN Web 开发 #6:AngularJS 在前端
- MEAN Web 开发 #7:MongoDB 和 Mongoose
- MEAN Web 开发 #8:Sockets 将让你大吃一惊!
- MEAN Web 开发 #9:一些最后的说明
一如既往,你可以在 我的 GitHub 页面 的 mean7-blog 仓库 中找到本帖的示例代码。
你好,持久化数据
我之前已经写过一篇关于 NoSQL 和 MongoDB 的完整文章,名为《初探 NoSQL 和 MongoDB》。在这篇文章系列的第一个部分,即《MEAN Web 开发 #1:MEAN,是什么以及为什么》 中,我曾建议你阅读它。如果你还没有读过这两篇文章,我建议你在继续之前先去阅读,因为我不会重复如何安装 MongoDB 和 MongoVUE。别担心,我等你……
在继续之前,我应该提到我们将要做的所有事情都是异步的。这意味着会用到大量的回调函数。毕竟,我们不想阻塞主线程!这也意味着回调函数可能不会按照它们“父函数”的顺序被调用。或者记录可能在我们查询之前就已经被插入了!因为我想在完整的示例文件中保持简单,所以我没有将所有示例都嵌套在回调函数中,但请记住,你可能会得到一些奇怪的结果。不过对我来说,它工作得很好。如果你得到一些奇怪的结果,可以尝试逐个运行示例(只需注释掉其他示例)。
那么,让我们快速启动并运行一个 Node.js 服务器,并将一些数据写入数据库吧!你会惊讶于它的简单。首先,使用 npm 安装 MongoDB 驱动程序(npm install mongodb)。
接下来,我们将连接到我们的 MongoDB 实例。
var app = require('express')();
var MongoClient = require('mongodb').MongoClient;
var urlWithCreds = 'mongodb://user:password@localhost:27017/local';
var url = 'mongodb://:27017/local';
MongoClient.connect(url, function (err, db) {
if (err) {
console.log(err);
} else {
console.log('Connected to the database.');
db.close();
}
});
var server = app.listen(80, '127.0.0.1');
所以,首先我们需要 Express(对于 MongoDB 来说不是必需的)和 MongoDB。我们从 MongoDB 模块中获取 MongoClient 属性。我们使用这个客户端通过 connect 函数连接到数据库,该函数接受一个 URI 和一个 callback 函数。该回调函数接收 MongoError(例如,如果你无法登录,因为凭据错误)和 Db 作为参数。
我们可以使用 Db 对象执行各种操作,例如创建和删除数据库、集合和索引,以及执行我们的 CRUD 操作(创建、读取、更新、删除)。让我们插入一个简单的对象。
var url = 'mongodb://:27017/local';
MongoClient.connect(url, function (err, db) {
if (err) {
console.log(err);
} else {
var artist = {
name: 'Massive Attack',
countryCode: 'GB'
};
var collection = db.collection('artists');
collection.insertOne(artist);
console.log(artist._id);
db.close();
}
});
正如你所见,我们使用 db 参数通过 collection 函数获取一个集合(MongoDB 中类似于数据库表的东西)。如果集合不存在,它会自动创建一个。然后,我们可以使用集合的 insertOne 函数简单地插入一个对象。现在发生了一些有趣的事情。调用 insertOne 之后,我们的 artist 对象突然有了一个 _id 属性。MongoDB 使用此 _id 来唯一标识对象。
这并不难,对吧?让我们看看其他的 CRUD 功能!
MongoDB 的 CRUD 操作
那么,让我们检索刚才插入的记录。我们可以使用集合的 findOne 函数来做到这一点。
MongoClient.connect(url, function (err, db) {
if (err) {
console.log(err);
} else {
var collection = db.collection('artists');
collection.findOne({ name: 'Massive Attack' }, function (err, artist) {
if (err) {
console.log(err);
} else {
console.log(artist);
}
db.close();
});
}
});
传递给 findOne 函数的对象实际上是一个搜索参数。在这种情况下,我们正在寻找 name 等于 'Massive Attack' 的文档(或记录)。第二个参数是一个回调函数,它会给我们一个错误(如果发生的话)以及检索到的文档。如果你多次运行前面的示例,Massive Attack 将在你的数据库中出现多次(具有不同的 _id 值),在这种情况下,findOne 只会返回它找到的第一个文档。
所以,让我们再插入几个艺术家,这样我们就有了一个可以操作的小数据集。我们可以使用 insertMany 函数。
collection.insertMany([
{
name: 'The Beatles',
countryCode: 'GB',
members: [
'John Lennon',
'Paul McCartney',
'George Harrison',
'Ringo Starr'
]
},
{
name: 'Justin Bieber',
countryCode: 'No one wants him'
},
{
name: 'Metallica',
countryCode: 'USA'
},
{
name: 'Lady Gaga',
countryCode: 'USA'
}
], function (err, result) {
if (err) {
console.log(err);
} else {
console.log(result);
}
});
现在你可能认为也存在一个 findMany 函数,但实际上它被称为 find。find 返回一个 Cursor,它有点像数组,但又不完全是。不过,我们可以使用 toArray 方法。find 函数有一个查询参数,它只是一个对象,描述文档的哪些字段必须具有哪些值。我们可以像在 SQL 数据库中那样,使用 AND、OR、NOT、IN、大于、小于、正则表达式以及你习惯的所有其他操作来搜索字段。
var findCallback = function (err, artists) {
if (err) {
console.log(err);
} else {
console.log('\n\nFound artists:');
artists.forEach(function (a) {
console.log(a);
});
}
};
// All documents.
collection.find().toArray(findCallback);
// Name not equal to Justin Bieber.
collection.find({ name: { $ne: 'Justin Bieber' } }).toArray(findCallback);
// Name equal to Massive Attach or name equal to The Beatles.
collection.find({ $or: [{ name: 'Massive Attack' }, { name: 'The Beatles' }] }).toArray(findCallback);
// Members contains John Lennon.
collection.find({ members: 'John Lennon' }).toArray(findCallback);
现在,让我们更新一条记录。
collection.findOneAndUpdate({ name: 'Massive Attack' },
{ $set: {
cds: [
{
title: 'Collected',
year: 2006,
label: {
name: 'Virgin'
},
comment: 'Best Of'
},
{
title: 'Mezzanine',
year: 1998,
label: 'Virgin'
},
{
title: 'No Protection: Massive Attack v Mad Professor',
year: 1995,
label: 'Circa Records',
comment: 'Remixes'
},
{
title: 'Protection',
year: 1994,
label: {
name: 'Circa'
}
}
]
}
}, function (err, result) {
console.log('\n\nUpdated artist:');
if (err) {
console.log(err);
} else {
console.log(result);
}
});
这里我们看到了 findOneAndUpdate 的作用。或者,我们也可以使用 updateOne。对于多个更新,我们可以使用 updateMany。
现在,让我们删除一条记录。有一个家伙我真的不希望出现在我的数据库里(是的,我添加了他,这样我就不会因为删除他而感到内疚)。为此,我们当然可以使用 findOneAndDelete。
collection.findOneAndDelete({ name: 'Justin Bieber' }, function (err, result) {
console.log('\n\nDeleted artist:');
if (err) {
console.log(err);
} else {
console.log(result);
}
});
这没什么好惊讶的。另外还有 deleteOne,要删除多个,你猜对了,是 deleteMany。
Mongoose
所以,MongoDB 与 Node.js 的集成看起来很棒,对吧?使用起来并不难。这真的只是处理 JavaScript 对象的问题。我们都知道 JavaScript 对象非常动态。在前面的示例中,我们已经看到有些艺术家有 member 属性,然后当我们更新时,突然又有了 cds 属性,一些 CD 有评论,而另一些则没有…… MongoDB 对此完全没有问题。我们只是保存和获取那里存在的数据。
现在试试这个。
var app = require('express')();
var MongoClient = require('mongodb').MongoClient;
var Artist = function (name, activeFrom, activeTo) {
if (!(this instanceof Artist)) {
return new Artist(name, activeFrom, activeTo);
}
var self = this;
self.name = name;
self.activeFrom = activeFrom;
self.activeTo = activeTo;
self.yearsActive = function () {
if (self.activeTo) {
return self.activeTo - self.activeFrom;
} else {
return new Date().getFullYear() - self.activeFrom;
}
};
};
var url = 'mongodb://:27017/local';
MongoClient.connect(url, function (err, db) {
if (err) {
console.log(err);
} else {
var collection = db.collection('artists');
// Empty the collection
// so the next examples can be run more than once.
collection.deleteMany();
var massiveAttack = new Artist('Massive Attack', 1988);
console.log('\n\n' + massiveAttack.name + ' has been active for ' + massiveAttack.yearsActive() + ' years.');
collection.insertOne(massiveAttack);
collection.findOne({ name: massiveAttack.name }, function (err, result) {
if (err) {
console.log(err);
} else {
try {
console.log('\n\n' + result.name + ' has been active for ' + result.yearsActive() + ' years.');
} catch (ex) {
console.log(ex);
}
}
});
}
});
var server = app.listen(80, '127.0.0.1');
发生的情况是,MongoDB 不存储 yearsActive 函数或 constructor 函数。MongoDB 只存储非函数值。结果是,当我们检索对象时,它将不再是 Artist 对象,而只是一个碰巧具有与 Artist 相同属性的对象。
这时 Mongoose 就派上用场了!Mongoose 为你的 MongoDB 对象添加了一个 schema。让我们看看它是如何工作的。
要将 Mongoose 添加到你的项目中,你可以使用 npm install mongoose 进行安装。
所以,首先我们可以使用 mongoose.connect 连接到数据库。
var app = require('express')();
var mongoose = require('mongoose');
var url = 'mongodb://:27017/local';
mongoose.connect(url);
var db = mongoose.connection;
db.on('error', function (err) {
console.log(err);
});
db.once('open', function (callback) {
// ...
});
var server = app.listen(80, '127.0.0.1');
之后,我们可以使用 Schema 函数定义一个 schema。
db.once('open', function (callback) {
var artistSchema = mongoose.Schema({
name: String,
activeFrom: Number,
activeTo: Number
});
artistSchema.methods.yearsActive = function () {
var self = this;
if (self.activeTo) {
return self.activeTo - self.activeFrom;
} else {
return new Date().getFullYear() - self.activeFrom;
}
};
});
正如你所见,我已经将 yearsActive 函数附加到 artistSchema.methods 对象。之后,我们可以使用 mongoose.model 创建一个 Model。
var Artist = mongoose.model('Artist', artistSchema);
在此之后,Artist 变量(一个 Model)实际上是你的集合的入口。它也是艺术家的构造函数。所以,让我们创建我们的 Massive Attack 艺术家。
var massiveAttack = new Artist({ name: 'Massive Attack', activeFrom: 1988 });
console.log('\n\n' + massiveAttack.name + ' has been active for ' + massiveAttack.yearsActive() + ' years.');
然后我们可以使用 save 函数保存它。
massiveAttack.save(function (err, result) {
if (err) {
console.log(err);
} else {
// ...
}
});
现在艺术家已经保存,让我们检索它并再次调用 yearsActive 函数。我们可以使用 Model.findOne 轻松检索我们的对象。
Artist.findOne({ name: massiveAttack.name }, function (err, result) {
if (err) {
console.log(err);
} else {
try {
console.log('\n\n' + result.name + ' has been active for ' + result.yearsActive() + ' years.');
} catch (ex) {
console.log(ex);
}
}
});
在这里,我将 findOne 直接放在 save 的回调函数中,这在之前我并没有这样做。我需要这样做,因为直接在 save 之后调用 findOne 没有返回任何结果(我猜是时序问题)。更重要的是,它成功执行了 yearsActive 函数!
与常规 MongoDB 驱动程序一样,我们可以使用 find、remove、findOneAndRemove 和 findOneAndUpdate。
所以,我们研究了 MongoDB 驱动程序以及 Mongoose 修复的无模式对象问题。我建议多加练习,因为删除、创建、插入、更新、读取和删除数据真的很容易,还可以阅读两者的 API 文档。我们在这里只触及了表面,但它已经为你指明了方向。
祝您编码愉快!