65.9K
CodeProject 正在变化。 阅读更多。
Home

MEAN Web 开发 #7:MongoDB 和 Mongoose

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2015 年 9 月 6 日

CPOL

6分钟阅读

viewsIcon

16036

MEAN Web 开发系列文章的第七篇。

上周我们了解了 AngularJS 的一些基本功能,至少足以让你上手。在此之前,我们已经学习了 Node.jsExpress。所以 EAN 已经有了,现在还剩下 M。好吧,Dial M for MongoDB,因为这正是我们这周要学习的内容。

  1. MEAN Web 开发 #1:MEAN 是什么以及为什么使用它
  2. MEAN Web 开发 #2:Node.js 在后端
  3. MEAN Web 开发 #3:更多 Node.js
  4. MEAN Web 开发 #4:Node.js Express 全面起航!
  5. MEAN Web 开发 #5:Jade 和 Express
  6. MEAN Web 开发 #6:AngularJS 在前端
  7. MEAN Web 开发 #7:MongoDB 和 Mongoose
  8. MEAN Web 开发 #8:Sockets 将让你大吃一惊!
  9. 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 驱动程序一样,我们可以使用 findremovefindOneAndRemovefindOneAndUpdate

所以,我们研究了 MongoDB 驱动程序以及 Mongoose 修复的无模式对象问题。我建议多加练习,因为删除、创建、插入、更新、读取和删除数据真的很容易,还可以阅读两者的 API 文档。我们在这里只触及了表面,但它已经为你指明了方向。

祝您编码愉快!

文章 MEAN Web 开发 #7:MongoDB 和 Mongoose 最早出现在 Sander's bits

© . All rights reserved.