Node.js 根据 Mongoose对MongoDB 进行增删查改(CRUD)操作

MongoDB是一个开源的NoSQL数据库,相比MySQL那样的关系型数据库,它更显得轻巧、灵活,非常适合在数据规模很大、事务性不强的场合下使用。同时它也是一个对象数据库,没有表、行等概念,也没有固定的模式和结构,所有的数据以文档的形式存储(文档,就是一个关联数组式的对象,它的内部由属性组成,一个属性对应的值可能是一个数、字符串、日期、数组,甚至是一个嵌套的文档。),数据格式就是JSON。

假定读者已经了解了mongdb和mysql的区别和为什么选用mongodb,继而介绍nodejs里的mongoose模块进行实战,具体分4步骤

  • mongoose是什么?

  • mongoose入门

  • mongoose概念

  • mongoose crud

(一)Mongoose是什么?

Mongoose是MongoDB的一个对象模型工具,是基于node-mongodb-native开发的MongoDB nodejs驱动,可以在异步的环境下执行。同时它也是针对MongoDB操作的一个对象模型库,封装了MongoDB对文档的的一些增删改查等常用方法,让NodeJS操作Mongodb数据库变得更加灵活简单。

Mongoose,因为封装了对MongoDB对文档操作的常用处理方法,可以高效的操作mongodb,同时可以理解mongoose是一个简易版的orm ,提供了类似schema定义,hook、plugin、virtual、populate等机制,让NodeJS操作Mongodb数据库变得特别简单!

以往书中往往直接上例子,混合各种库和代码,容易让人晕,必须在例子中才能知道m是如何使用的,我一直认为这是不合理的,为什么我要掌握其他的知识才能学mongoose?

其实,它也仅仅是一个node模块而已。

官网:http://mongoosejs.com/

(二)mongoose入门

前面我们已经认识了Mongoose,也了解了MongoDB,回顾一下:MongoDB是一个对象数据库,是用来存储数据的;Mongoose是封装了MongoDB操作的一个对象模型库,是用来操作这些数据的。

好,下面我们就来进行操作数据的第一步吧。

准备

1、 安装mongoose

$ npm install --save mongoose

2、 引用mongoose

var mongoose = require("mongoose");

3、 使用"mongoose"连接数据库

var db = mongoose.connect("mongodb://user:pass@ip:port/database");

说明

  • user 是mongodb里用户名

  • pass 是mongodb里用户对应的密码

  • ip 是mongodb服务器可以访问ip地址,比如本地为127.0.0.1

  • port 是mongodb服务器可以访问端口,默认是27017

测试

执行下面代码检查默认数据库test,是否可以正常连接成功?

var mongoose = require("mongoose");
var db = mongoose.connect("mongodb://127.0.0.1:27017/db_helloworld"); 
db.connection.on("error", function (error) {  
  console.log("数据库连接失败:" + error); 
}); 
db.connection.on("open", function () {  
  console.log("数据库连接成功"); 
});

执行代码

$ node book-source/db/helloworld/connect.js
数据库连接成功

当mongodb没有启动的时候会报错,当出现如下问题,请执行mhg启动mongodb即可

$ node book-source/db/helloworld/connect.js
数据库连接失败:MongoError: connect ECONNREFUSED 127.0.0.1:27017

最小demo

这里给出极简demo,用于讲解mongoose从连接数据库到对数据库进行操作完整过程,这样更容易让读者了解核心原理,代码如下:

// 1、引入`mongoose`模块
var mongoose = require('mongoose');

// 2、通过`mongoose.connect`连接mongodb数据库
mongoose.connect('mongodb://127.0.0.1/db_helloworld');

// 3、通过`mongoose.model`定义模型(model)
var Cat = mongoose.model('Cat', { name: String });

// 4、通过`new`关键字实例化Cat模型,参数是`{ name: 'Zildjian' }`,创建kitty对象
var kitty = new Cat({ name: 'Zildjian' });

// 5、执行`kitty.save`来保存到数据库
kitty.save(function (err) {
  if (err) {
    console.log('save error:' + err);
  }
  console.log('save sucess');
});

核心步骤说明

  • 定义模型(model)

  • 通过new关键字实例化Cat模型,创建kitty对象

  • 执行kitty.save来保存到数据库

这个其实就是mongoose最常见的用法,首先约定schema,即在模型model定义的时候指定字段和字段类型,避免乱用schema-free问题。之后对实例化模型创建的对象进行操作,完成我们常见的增删改查功能。

模型(model)定义即定义对象,对象操作即对数据库进行操作

执行如下

$ node book-source/db/helloworld/helloworld.js

如无错误日志,即代表数据保存成功。此时打开robo mongodb客户端查看一下具体数据是否保存成功。

WX20171222-164001@2x.jpg

实例演示

  • 连接数据库信息放到独立文件里

  • 模型定义放到独立文件

  • 在具体调用的文件里,使用模型定义

连接数据库

看一下实际代码,db/mini/connect.js

var mongoose = require("mongoose");

var db = mongoose.connect("mongodb://127.0.0.1:27017/db_helloworld"); 

db.connection.on("error", function (error) {  
  console.log("数据库连接失败:" + error); 
}); 

db.connection.on("open", function () {  
  console.log("数据库连接成功");
});

一般项目里,所有模型都共用一个数据库连接信息,所以把连接数据库的代码抽取到connect.js里,然后在对应的模型里会app入口引用即可。

模型定义

看一下实际模型定义代码,db/mini/user.js

var mongoose = require('mongoose');

// 定义Schema
UserSchema = new mongoose.Schema({
  username: {// 真实姓名
    type: String,           // 类型
    required: true
  },
  password: { // 密码
    type: String,
    required: true
  }
});

// 定义Model
var UserModel = mongoose.model('User', UserSchema);

// 暴露接口
module.exports = UserModel;

这是MVC里Model层最长见的代码,没有连接信息,也没有其他额外不相干代码,当你看到user.js你就能理解它在数据库里对应的表结构,以及字段的类型、约束等信息,一般来说,代码写的越干净,可读性会更好一些。

这里定义的User模型里只有 用户名 和 密码 2个字段,它们都必须有值的,也就是说当你创建用户的时候,没有密码或者没有用户名你是无法创建成功的。

除了常规,还有字段设定的其他函数,比如下面的示例

UserSchema = new mongoose.Schema({
  username: {// 真实姓名
    type: String,           // 类型
    required: true,         // 必填
    default: 'hello',       // 设置默认值
    unique: true,           // 唯一索引
    trim: true,             // 表示存储时去除两边空格
    set:function(value){    // 类似java的setter方法
        if(value.indexof('X')){
            return 'XXX'
        }
        return value;
    },
    get: function(value){
        // 类似java的getter方法
    }
  },
  password: { // 密码
    type: String,
    required: true,
    index: true        // 辅助索引
  }
});

初次之外,还可以设置虚拟属性Schema.virtual()、toJSON、唯一索引、辅助索引等等,参考极客学院视频:http://www.jikexueyuan.com/course/1905.html

测试代码

看一下实际代码db/mini/user.js

// 1、引入`mongoose connect`
require('./connect');

// 2、引入`User` Model
var User = require('./user');

// 3、定义`user` Entity
var user = new User({
  username: 'i5ting',
  password: '0123456789'
});

// 4、对数据库进行操作
user.save(function(err, doc){
  if (err) {
    console.log('save error:' + err);
  }
  
  console.log('save sucess \n' + doc);
})

核心步骤

  • 引入数据库连接,保证mongodb已经连接成功

  • 引入模型(model)定义文件,即文档(表)结构定义

  • 实例化UserModel,创建user实体

  • 最后通过user实体对数据库进行操作,完成用户注册功能。

这是项目里数据访问层的代码,它真实的对数据库进行操作,所以它一般会出现在controllerservice层。

执行测试

$ node db/mini/test.js 
数据库连接成功
save sucess 
{ _id: 57341fc54d97ee0249082a1d,
  password: '0123456789',
  username: 'i5ting',
  __v: 0 }

(三)mongoose概念

结合上面的实例来讲4个核心概念,以便于理解

  • ORM 对象关系映射

  • Schema

  • Model 模型

  • Entity 实体

对象关系映射

对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。如今已有很多免费和收费的ORM产品,而有些程序员更倾向于创建自己的ORM工具。

面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为了解决这个不匹配的现象,对象关系映射技术应运而生。

对象关系映射(Object-Relational Mapping)提供了概念性的、易于理解的模型化数据的方法。

ORM方法论基于三个核心原则:

  • 简单:以最基本的形式建模数据。

  • 传达性:数据库结构被任何人都能理解的语言文档化。

  • 精确性:基于数据模型创建正确标准化的结构。

典型地,建模者通过收集来自那些熟悉应用程序但不熟练的数据建模者的人的信息开发信息模型。建模者必须能够用非技术企业专家可以理解的术语在概念层次上与数据结构进行通讯。建模者也必须能以简单的单元分析信息,对样本数据进行处理。ORM专门被设计为改进这种联系。

让我们从O/R开始。字母O起源于 对象(OBJECT),而R则来自于 关系(RELATIONAL)。几乎所有的程序里面,都存在对象和关系数据库。在业务逻辑层和用户界面层中,我们是面向对象的。当对象信息发生变化的时候,我们需要把对象的信息保存在关系数据库中。

最简单的理解:

ORM是让用语言中的对象来操作数据库,至于如何实现就是orm工具实现的,可以理解mongoose是orm工具。

mongoose包括以下四部分:

  • 一个对持久类对象进行CRUD操作的API,可以理解为实体Entity上的方法

  • 一个语言或API用来规定与类和类属性相关的查询,比如Population

  • 一个规定MAPPING METADATA的工具,可以理解为Schema定义

  • 一种技术可以让ORM的实现各种db操作的封装

Schema

Schema是一种以文件形式存储的数据库模型骨架,无法直接通往数据库端,也就是说它不具备对数据库的操作能力,仅仅只是定义数据库模型在程序片段中的一种表现,可以说是数据属性模型(传统意义的表结构),又或着是“集合”的模型骨架。

最简单的理解:

Schema是对文档(表)结构的定义

那如何去定义一个Schema呢,请看示例: UserSchema包含4个属性 [name, age, time, email]

var mongoose = require("mongoose");

/* 定义一个 Schema */
var UserSchema = new mongoose.Schema({
    name : { type:String, required: true},//属性name,类型为String, 必填
    age  : { type:Number, default:0 },//属性age,类型为Number,默认为0
    time : { type:Date, default:Date.now },
    email: { type:String,default:''}
});

基本属性类型有:字符串、日期型、数值型、布尔型(Boolean)、null、数组、内嵌文档等,当然它还有更丰富的对字段进行校验约束的功能。

模型(Model)

模型(Model)是由Schema构造生成的模型,除了Schema定义的数据库骨架以外,还具有数据库操作的行为,类似于管理数据库属性、行为的类。

如何通过Schema来创建Model呢,如下示例:

var db = mongoose.connect("mongodb://127.0.0.1:27017/test");  
// 创建Model 
var TestModel = db.model("test1", TestSchema);

// 定义Model
var UserModel = mongoose.model('User', UserSchema);

User是模型名称,它对应到mongodb里就是数据库中的集合名称,默认会转成复数,变为'users',当我们对其添加数据时如果users已经存在,则会保存到其目录下,如果未存在,则会创建users集合,然后在保存数据。

拥有了Model,我们也就拥有了操作数据库的金钥匙,在后面的内容中,我们就会学习使用Model来进行增删改查的具体操作,所以,一定要熟悉他的创建格式哟!

如果你想对某个集合有所作为,那就交给Model模型来处理吧,创建一个Model模型,我们需要指定:1.集合名称,2.集合的Schema结构对象,满足这两个条件,我们就会拥有一个操作数据库的金钥匙。

实体(Entity)

实体(Entity)是由Model创建的实体,使用save方法保存数据,Model和Entity都有能影响数据库的操作,但Model比Entity更具操作性。

使用Model创建Entity,如下示例:

var user = new User({
  username: 'i5ting',
  password: '0123456789'
});
console.log(user.username); // i5ting 
console.log(user.password); // 0123456789

创建成功之后,Schema属性就变成了Model和Entity的公共属性了。

游标

MongoDB 使用游标返回find的执行结果.客户端对游标的实现通常能够对最终结果进行有效的控制。可以限制结果的数量,略过部分结果,根据任意键按任意顺序的组合对结果进行各种排序,或者是执行其他一些强的操作。

  • limit(3)  限制返回结果的数量,

  • skip(3) 跳过前3个文档,返回其余的

  • sort( {“username”:1 , “age”:-1 } )  排序 键对应文档的键名, 值代表排序方向, 1 升序, -1降序

ObjectId

存储在mongodb集合中的每个文档(document)都有一个默认的主键_id,这个主键名称是固定的,它可以是mongodb支持的任何数据类型,默认是ObjectId。

ObjectId是一个12字节的 BSON 类型字符串。按照字节顺序,依次代表:

  • 4字节:UNIX时间戳

  • 3字节:表示运行MongoDB的机器

  • 2字节:表示生成此_id的进程

  • 3字节:由一个随机数开始的计数器生成的值

总结

Schema是骨架,模型(model)是根据Schema创建的模板,也就是说Schema和Model是定义部分,而实体Entity是模型实例化后创建的对象,它才是真正对数据库进行操作的。

所以我们会把定义部分(Schema + model)和实体操作部分(Entity)分开,定义是不变的,而实体是对数据库进行操作,操作类是术语可变的,所以在mvc分层的时候model实际放的是定义部分,而在controller里使用的是实体操作部分的。

基于前面的内容,接下来我们就开始学习对数据的具体操作了,下面是关于一些基础数据的定义,相信对于你来说已经不陌生了,请在仔细温习一遍吧!

Schema - 表结构

1、构造函数

new mongoose.Schema( { name:{type:String}, age:{type:Number, default:10}  } )

2、添加属性

Schema.add( { name: ‘String’, email: ‘String’, age: ‘Number’ } )

3、有时候Schema不仅要为后面的Model和Entity提供公共的属性,还要提供公共的方法

Schema.method( ‘say’, function(){console.log(‘hello’);} )
//这样Model和Entity的实例就能使用这个方法了

4、添加静态方法

Schema.static( ‘say’, function(){console.log(‘hello’);} )
//静态方法,只限于在Model层就能使用

5、追加方法

Schema.methods.say = function(){console.log(‘hello’);};
//静态方法,只限于在Model层就能使用

model - 文档操作

1、构造函数, 参数1:集合名称, 参数2:Schema实例

db.model(“test1”, TestSchema );

2、查询, 参数1忽略,或为空对象则返回所有集合文档

model.find({}, callback);
model.find({},field,callback);          // 过滤查询,参数2: {‘name’:1, ‘age’:0} 查询文档的返回结果包含name , 不包含age.(_id默认是1)
model.find({},null,{limit:20});         // 过滤查询,参数3: 游标操作 limit限制返回结果数量为20个,如不足20个则返回所有.
model.findOne({}, callback);            // 查询找到的第一个文档
model.findById(‘obj._id’, callback);    // 查询找到的第一个文档,同上. 但是只接受 __id 的值查询

3、创建, 在集合中创建一个文档

Model.create(文档数据, callback))

4、更新,参数1:查询条件, 参数2:更新对象,可以使用MondoDB的更新修改器

Model.update(conditions, update, function(error)

5、删除, 参数1:查询条件

Model.remove(conditions,callback);

Entity - 文档操作

1、构造函数, 其实就是model的实例

new TestModel( { name:‘xueyou’, age:21 } );

2、创建, 在集合中创建一个文档.

Entity.save(callback);

修改器和更新器

更新修改器:

‘$inc’ 增减修改器,只对数字有效.下面的实例: 找到 age=22的文档,修改文档的age值自增1

Model.update({‘age’:22}, {’$inc’:{‘age’:1} }  );    // 执行后: age=23

‘$set’ 指定一个键的值,这个键不存在就创建它.可以是任何MondoDB支持的类型.

Model.update({‘age’:22}, {’$set’:{‘age’:‘haha’} }  );    // 执行后: age=‘haha’

‘$unset’ 同上取反,删除一个键

Model.update({‘age’:22}, {’$unset’:{‘age’:‘haha’} }  );    // 执行后: age键不存在

数组修改器:

‘$push’ 给一个键push一个数组成员,键不存在会创建

Model.update({‘age’:22}, {’$push’:{‘array’:10} }  );    // 执行后: 增加一个 array 键,类型为数组, 有一个成员 10

‘$addToSet’ 向数组中添加一个元素,如果存在就不添加

Model.update({‘age’:22}, {’$addToSet’:{‘array’:10} }  );    // 执行后: array中有10所以不会添加

‘$each’ 遍历数组, 和 $push 修改器配合可以插入多个值

Model.update({‘age’:22}, {’$push’:{‘array’:{’$each’: [1,2,3,4,5]}} }  );    // 执行后: array : [10,1,2,3,4,5]

‘$pop’ 向数组中尾部删除一个元素

Model.update({‘age’:22}, {’$pop’:{‘array’:1} }  );    // 执行后: array : [10,1,2,3,4]  tips: 将1改成-1可以删除数组首部元素

‘$pull’ 向数组中删除指定元素

Model.update({‘age’:22}, {’$pull’:{‘array’:10} }  );    // 执行后: array : [1,2,3,4]  匹配到array中的10后将其删除

条件查询:

  • “$lt” 小于

  • “$lte”  小于等于

  • “$gt” 大于

  • “$gte”  大于等于

  • “$ne” 不等于

Model.find({“age”:{ “$get”:18 , “$lte”:30 } } );    // 查询 age 大于等于18并小于等于30的文档

或查询 OR:

  • ‘$in’ 一个键对应多个值

  • ‘$nin’ 同上取反, 一个键不对应指定值

  • “$or” 多个条件匹配, 可以嵌套 $in 使用

  • “$not”  同上取反, 查询与特定模式不匹配的文档

Model.find({“age”:{ “$in”:[20,21,22.‘haha’]} } );              // 查询 age等于20或21或21或’haha’的文档
Model.find({"$or" :  [ {‘age’:18} , {‘name’:‘xueyou’} ] });    // 查询 age等于18 或 name等于’xueyou’ 的文档

类型查询:

null 能匹配自身和不存在的值, 想要匹配键的值 为null, 就要通过  “$exists” 条件判定键值已经存在 "$exists" (表示是否存在的意思)

Model.find(“age” :  { “$in” : [null] , “exists” : true  } );     // 查询 age值为null的文档
Model.find({name: {$exists: true}},function(error,docs){         //查询所有存在name属性的文档});
Model.find({telephone: {$exists: false}},function(error,docs){   //查询所有不存在telephone属性的文档});

正则表达式:

MongoDb 使用 Prel兼容的正则表达式库来匹配正则表达式

find( {“name” : /joe/i } )      // 查询name为 joe 的文档, 并忽略大小写
find( {“name” : /joe?/i } )     // 查询匹配各种大小写组合

查询数组:

Model.find({“array”:10} );        // 查询 array(数组类型)键中有10的文档,  array : [1,2,3,4,5,10]  会匹配到
Model.find({“array[5]”:10} );     // 查询 array(数组类型)键中下标5对应的值是10,  array : [1,2,3,4,5,10]  会匹配到

‘$all’ 匹配数组中多个元素

Model.find({“array”:[5,10]} );    // 查询 匹配array数组中 既有5又有10的文档

‘$size’ 匹配数组长度

Model.find({“array”:{"$size" : 3} } );        // 查询 匹配array数组长度为3 的文档

‘$slice’ 查询子集合返回

Model.find({“array”:{"$skice" : 10} } );         // 查询 匹配array数组的前10个元素
Model.find({“array”:{"$skice" : [5,10] } } );    // 查询 匹配array数组的第5个到第10个元素

(四)CRUD(增删改查)

CRUD为数据库的最常见的4种基本操作,即增加(Create)、读取(Retrieve)(重新得到数据)、更新(Update)和删除(Delete)几个单词的首字母简写。主要被用在描述软件系统中数据库或者持久层的基本操作功能。

mongoose提供如下的crud方法

  • save

  • find | findOne

  • update

  • remove

下面我们使用user模型为例,给出具体例子

增加(Create)

文档

Model#save(product,)
@description Saves this document.
Parameters:
- product, <function(err, > Number)} [fn] optional callback
Returns:
    <Promise> Promise

具体代码

var MyStudent = mongoose.model("Student");

var sam = new MyStudent({
  name: "sam976",
  id: 123,
  phone: "18706888888",
  date: Date.now()
});

sam.save(function(err) {
    console.log('save status:', err ? 'failed' : 'success');
});

读取(Retrieve)

find:根据条件查询,返回的是数组

文档

Model.find(conditions, [fields], [options], [callback])
Finds documents
Parameters:
- conditions <Object>
- [fields] <Object> optional fields to select
- [options] <Object> optional
- [callback] <Function>
Returns:
    <Query>

代码

var MyStudent = mongoose.model("Student");
MyStudent.find({}, function(err, docs) {
    if (err) {
        console.log('err: ', err);
        return;
    }
    console.log('result: ', docs);
});

findOne:根据条件查询,返回的是一条数据对象

同样的还有FindByid

Model.findById(id, [projection], [options], [callback]);
Model.findOne([conditions], [projection], [options], [callback]);

文档

Model.findOne([conditions], [fields], [options], [callback])
Finds one document.
Parameters:
- [conditions] <Object>
- [fields] <Object> optional fields to select
- [options] <Object> optional
- [callback] <Function>
Returns:
    <Query>

代码

// 查询单个,单个对象
Book.findOne({author: 'TingFeng'}, function (err, doc) {
    if (err) {
        console.log('err: ', err);
        return;
    }

    // 修改
    // doc.author = 'X-rapido';
    // doc.save();

    // 删除
    // if (doc) {
    //     doc.remove();
    // }

    console.log('result: ', doc);
});

更新(Update)

findByIdAndUpdate:根据ID查找并更新

文档说明如下

Model.findByIdAndUpdate(id, [update], [options], [callback])
Issues a mongodb findAndModify update command by a documents id.
show code
Parameters:
- id <ObjectId, HexId> an ObjectId or string that can be cast to one.
- [update] <Object>
- [options] <Object>
- [callback] <Function>
Returns:
    <Query>

findOneAndUpdate:根据查询条件查找并更新

Model.findOneAndUpdate([conditions], [update], [options], [callback])
Issues a mongodb findAndModify update command.
Parameters:
- [conditions] <Object>
- [update] <Object>
- [options] <Object>
- [callback] <Function>
Returns:
    <Query>

删除(Delete)

文档

Model.remove(conditions, [callback])
Removes documents from the collection.
Parameters:
- conditions <Object>
- [callback] <Function>
Returns:
    <Promise> Promise

调试模式

调试模式是mongoose提供的一个非常实用的功能,用于查看mongoose模块对mongodb操作的日志,一般开发时会打开此功能,以便更好的了解和优化对mongodb的操作。

打开调试的核心代码是设置 debug 变量值为 true 即可

var mongoose = require("mongoose");

// 核心代码,是否开启测试
mongoose.set('debug', true);

var db = mongoose.connect("mongodb://127.0.0.1:27017/db_helloworld"); 
db.connection.on("error", function (error) {  
  console.log("数据库连接失败:" + error); 
}); 
db.connection.on("open", function () {  
  console.log("数据库连接成功");
});

总结

从mongoose是什么,到如何使用,以及核心概念(orm以及schema、model、entity),最后给出CRUD操作,希望读者能够认真体会orm和具体分层含义。

本节以最简单的user,结合ava完成单元测试,我们测试数据库代码并不一定要在koa或其他框架内部,最小化问题,mongoose它只是一个node模块,这样对于我们理解它是比较好的方式。接下来我们会讲解更多高级mongoose技巧,它们的基础都是本节内容,所以本节必须掌握,是本章重点。

参考文献:

Node.js 手册查询-3-Mongoose 方法

Mongoose Schemas v4.5.8

mongoose入门

http://alexeypetrushin.github.io/mongo-lite/docs/index.html





未经允许请勿转载:程序喵 » Node.js 根据 Mongoose对MongoDB 进行增删查改(CRUD)操作

点  赞 (2) 打  赏
分享到: