MongoDB & Mongoose

齐胜涝
2023-12-01

MongoDB 和 Mongoose

mongoose

建立一个 MongoDB Atlas 数据库并导入连接到它所需的软件包。将 mongodb@~3.6.0mongoose@~5.4.0 添加到项目的 package.json 中。 然后,在 myApp.js 文件中请求 mongoose。 创建一个 .env 文件,给它添加一个 MONGO_URI 变量。 变量的值为 MongoDB Atlas 数据库 URI。 应用单引号或双引号包裹 URI。请记住,环境变量 = 两边不能有空格。 例如应该这样写:MONGO_URI='VALUE'。 完成后,使用下面的代码来连接数据库。

mongoose.connect(<Your URI>, { useNewUrlParser: true, useUnifiedTopology: true });
const mongoose = require('mongoose');
mongoose.connect("mongodb+srv://<username>:<password>@cluster0.zbwns.mongodb.net/myFirstDatabase?retryWrites=true&w=majority", { useNewUrlParser: true, useUnifiedTopology: true });

创建模型 Model

C RUD 第一小节——CREATE

首先,需要一个 Schema, 每一个 Schema 都对应一个 MongoDB 的 collection, 并且在相应的 collection 里定义 documents 的“样子”。 Schema 用于组成模型(Model), 可以通过嵌套 Schema 来创建复杂的模型。可以根据模型创建实例,模型实例化后的对象称为 documents。

handler 函数会在特定事件(比如调用服务器 API)发生时执行。 done() 是一个回调函数,它的作用是在一个异步操作(比如对数据库进行插入、查询、更新或删除)执行完成时,告知可以继续执行后续的其它代码。 这与 Node.js 中的处理方式十分类似,在 Node.js 中,在(异步操作)成功时调用 done(null, data),在失败时调用 done(err)

注意:与远程服务器进行交互时,需要考虑到发生错误的可能!

/* Example */
const someFunc = function(done) {
  //... do something (risky) ...
  if (error) return done(error);
  done(null, result);
};

按下面的原型信息创建一个名为 personSchema 的 schema:

- Person Prototype -
--------------------
name : string [required]
age :  number
favoriteFoods : array of strings (*)

采用 Mongoose 基础 schema 类型。 如果还想添加更多的键,就请使用 required 或 unique 等简单的验证器(validators),并设置默认值。 详情请参考 Mongoose 文档

请从 personSchema 创建一个名为 Person 的 model。

const Schema = mongoose.Schema;
const personSchema = new Schema({
  name: { type: String, required: true },
  age: Number,
  favoriteFoods: [String]
});
const Person = mongoose.model("Person", personSchema);

创建并保存一条 Model 记录

createAndSavePerson 函数中,使用在上一个挑战中写好的 Person 构造函数创建 document 实例, 将包含 nameagefavoriteFoods 的对象传给构造函数, 这些属性的数据类型必须符合在 personSchema 中定义的类型。 然后在返回的 document 实例上调用方法 document.save()。 同时,按 Node.js 的方式为它传一个回调函数。 这是一种常见模式,以下所有 CRUD 方法都将这样的回调函数作为最后一个参数。

/* Example */
// ...
person.save(function(err, data) {
  //   ...do your stuff here...
});
let createAndSavePerson = function(done) {
  let janeFonda = new Person({name: "Jane Fonda", age: 84, favoriteFoods: ["eggs", "fish", "fresh fruit"]});

  janeFonda.save(function(err, data) {
    if (err) return                                             console.error(err);
    done(null, data)
  });
};

model.create() 创建多条记录

在一些情况下,比如进行数据库初始化,会需要创建很多 model 实例来用作初始数据。 Model.create() 接受一组像 [{name: 'John', ...}, {...}, ...] 的数组作为第一个参数,并将其保存到数据库。

let arrayOfPeople = [
  {name: "Frankie", age: 74, favoriteFoods: ["Del Taco"]},
  {name: "Sol", age: 76, favoriteFoods: ["roast chicken"]},
  {name: "Robert", age: 78, favoriteFoods: ["wine"]}
];

let createManyPeople = function(arrayOfPeople, done) {
  Person.create(arrayOfPeople, function (err, people) {
    if (err) return console.log(err);
    done(null, people);
  });
};

model.find() 查询数据库

C R UD 第二小节—— Read 查询

尝试一种最简单的用法,Model.find() 接收一个查询 document(一个 JSON 对象)作为第一个参数,一个回调函数作为第二个参数, 它会返回由匹配到的数据组成的数组。 这个方法支持很多搜索选项, 详情请参阅文档。

let findPeopleByName = function(personName, done) {
  Person.find({name: personName}, function (err, personFound) {
    if (err) return console.log(err);
    done(null, personFound);
  });
};

model.findOne() 返回一个单一匹配

Model.findOne()Model.find() 十分类似,但就算数据库中有很多条数据可以匹配查询条件,它也只返回一个 document,而不会返回一个数组, 如果查询条件是声明为唯一值的属性,它会更加适用。

let findOneByFood = function(food, done) {
  Person.findOne({favoriteFoods: food}, function (err, data) {
    if (err) return console.log(err);
    done(null, data);
  });
};

model.findById() 根据 _id 搜索

在保存 document 的时候,MongoDB 会自动为它添加 _id 字段,并给该字段设置一个唯一的仅包含数字和字母的值。 通过 _id 搜索是一个十分常见的操作,为此,Mongoose 提供了一个专门的方法。

var findPersonById = function(personId, done) {
  Person.findById(personId, function (err, data) {
    if (err) return console.log(err);
    done(null, data);
  });
};

通过执行查询、编辑、保存来执行经典更新流程

CR U D 第三小节—— Update 更新

在过去,如果想要编辑 document 并以某种方式使用它(比如放到服务器的返回数据中),就必须执行查找、编辑和保存。 Mongoose 有一个专用的更新方法 Model.update(), 它与底层的 mongo 驱动绑定。 通过这个方法,可以批量编辑符合特定条件的多个 document。但问题在于,这个方法不会返回更新后的 document,而是返回状态信息。 此外,它直接调用 mongo 的底层驱动,让处理 model 的验证变得更加棘手。

const findEditThenSave = (personId, done) => {
  const foodToAdd = 'hamburger';
  Person.findById(personId, (err, person) => {
    if(err) return console.log(err);   person.favoriteFoods.push(foodToAdd);
    person.save((err, updatedPerson) => {
      if(err) return console.log(err);
      done(null, updatedPerson)
    })
  })
};

在 document 中执行新的更新方式——使用 model.findOneAndUpdate()

最近发布的 mongoose 版本简化了 document 的更新方式, 但同时,一些高级功能(如 pre/post hook, 验证)的使用方式也变得和以前不同。因此,在很多情景下,上一个挑战中提到的老方法其实更常用。 新方法的加入,可以让我们使用 findByIdAndUpdate() 来进行基于 id 的搜索。

const findAndUpdate = (personName, done) => {
  const ageToSet = 20;

  Person.findOneAndUpdate({name: personName}, {age: ageToSet}, {new: true}, (err, updatedDoc) => {
    if(err) return console.log(err);
    done(null, updatedDoc);
  })
};

提示: 需要返回更新后的 document。 可以把 findOneAndUpdate() 的第三个参数设置为 { new: true } 。 默认情况下,这个方法会返回修改前的数据。

model.findByIdAndRemove 删除一个 document

CRU D 第四小节—— Delete 删除

findByIdAndRemovefindOneAndRemove 类似于之前的更新方法, 它们将被删除的 document 传给数据库。 和之前一样,使用函数参数 personId 作为查询关键字。

修改 removeById 函数,通过 _id 删除一个人的数据, 可以使用 findByIdAndRemove()findOneAndRemove() 方法。

let removeById = function(personId, done) {
  Person.findByIdAndRemove(
    personId,
    (err, removedDoc) => {
      if(err) return console.log(err);
      done(null, removedDoc);
    }
  ); 
};

model.remove() 删除多个 document

Model.remove() 可以用于删除符合给定匹配条件的所有 document。

修改 removeManyPeople 函数,使用 nameToRemove 删除所有姓名是变量 Model.remove() 的人。 给它传入一个带有 name 字段的查询 document 和一个回调函数。

注意: Model.remove() 不会返回被删除的 document,而是会返回一个包含操作结果以及受影响的数据数量的 JSON 对象。 不要忘记将它传入 done() 回调函数,因为我们需要在挑战的测试中调用它。

const removeManyPeople = (done) => {
  const nameToRemove = "Mary";
  Person.remove({name: nameToRemove}, (err, response) => {
    if(err) return console.log(err);
    done(null, response);
  })
};

链式调用辅助查询函数来缩小搜索结果

如果不给 Model.find()(或者别的搜索方法)的最后一个参数传入回调函数, 查询将不会执行。 可以将查询条件存储在变量中供以后使用, 也可以通过链式调用这类变量来构建新的查询字段。 实际的数据库操作会在最后调用 .exec() 方法时执行。 必须把回调函数传给最后一个方法。 Mongoose 提供了许多辅助查询函数, 这里使用最常见的一种。

修改 queryChain 函数来查询喜欢 foodToSearch 食物的人。 同时,需要将查询结果按 name 属性排序, 查询结果应限制在两个 document 内,并隐藏 age 属性。 请链式调用 .find().sort().limit().select(),并在最后调用 .exec(), 并将 done(err, data) 回调函数传入 exec()

let queryChain = function(done) {
  var foodToSearch = "burrito";
  var jsonObject = {favoriteFoods : foodToSearch};
  Person.find(jsonObject).sort({name: 1}).limit(2).select({age: 0}).exec((err, data) => {
    (err) ? done(err) : done(null, data); 
  })
};
 类似资料: