MongoDB学习笔记

一、数据库

数据库是按照数据结构来组织、存储和管理数据的仓库

程序都是在内存中运行的,一旦程序运行结束或计算机断电,程序运行中的数据都会丢失,所以需要将一些程序运行的数据持久化到硬盘中,以确保数据的安全性,而数据库是数据持久化的最佳选择

数据库包含数据库服务器和数据库客户端:

— 数据库服务器用来保存数据

— 数据库客户端用来操作服务器,对数据进行增删改查的操作

数据库分类

数据库分为两种:

— 关系型数据库(RDBMS)

关系型数据库中全都是表,如 MySQL、Oracle、DB2、SQL Server…

所有关系型数据库使用 SQL(结构化查询语言)来操作

关系型数据库每操作一次就要连接一次(因为有事务控制)

— 非关系型数据库(No SQL ———— Not Only SQL)

键值对数据库,如 MongoDB(文档数据库)、Redis…

只需连接一次,连接一次后,除非项目停止服务器关闭,否则连接一般不会断开

数据库、集合、文档

数据库集合文档

数据库

数据库是一个仓库,仓库中可以存放集合

一个数据库服务器中可以有多个数据库

集合

集合类似于数组,集合就是一组文档,集合是用来存放文档的

集合中存储的文档可以是各种各样的,没有格式要求

文档

文档是文档数据库中的最小单位,我们存储和操作的内容都是文档

文档类似于 JS 中的对象,在 MongoDB 中每一条数据都是一个文档

二、MongoDB

MongoDB 是为快速开发互联网 Web 应用而设计的数据库系统

MongoDB 的设计目标是极简、灵活、作为 Web 应用栈的一部分

MongoDB 的数据模型是面向文档的,所谓文档是一种类似于 JSON 的结构,MongoDB 中存的是 JSON 和 二进制数据(BSON)

MongoDB 版本中偶数版为稳定版,奇数版为开发版,MongoDB 对 32 位系统支持不佳,在 3.2 版本后没有再对 32 位系统的支持

1、下载和安装

(1)在官网下载并安装 –>

(2)配置环境变量(添加安装包的 bin 目录到 path 中) –>

(3)在磁盘创建 data/db 文件夹 –>

(4)在命令行输入 mongod 启动数据库服务器(32 位第一次启动需输入 mongod --storageEngine=mmapvl) –>

(5)在命令行输入 mongo 启动数据库客户端连接 mongodb

也可通过 mongod --dbpath 路径 --port 端口号 指定 data/db 文件夹路径和端口

MongoDB 自启动

将 MongoDB 设置为系统服务,可以自动在后台启动,无需每次手动启动

(1)创建 data/db 和 data/log 文件夹

(2)在安装路径 xxx\MongoDB\Server\x.x 下新建文件 mongod.cfg,在文件中添加如下内容

systemLog:
    destination: file
    path: xxx\data\log\mongod.log
storage:
    dbPath: xxx\data\db

(3)以管理员身份打开命令行,执行

sc.exe create MongoDB binPath="\"xxxx\MongoDB\Server\x.x\bin\mongod.exe\" --service --config=\"xxx\MongoDB\Server\x.x\mongod.cfg\"" DisplayName="MongoDB" start="auto"

(4)在任务管理启动右键 “MongoDB” 点击 “启动”

注意若启动失败,在命令行输入 sc delete MongoDB 删除之前配置的服务,从第一步再来一遍

2、注意事项

在 MongoDB 中,数据库和集合都不需要手动创建,当创建文档时,若文档所在的集合或数据库不存在会自动创建数据库和集合

所以在 MongoDB 中,即使数据库不存在也能使用 use 指令进入数据库,当向该数据库中插入第一条文档时会自动创建该数据库和集合

MongoDB 的文档的属性值也可以是一个文档,当一个文档的属性值为一个文档时,称属性值的这个文档为内嵌文档

3、基本指令

show dbsshow databases 显示所有数据库

use 数据库名 进入指定数据库

db 显示当前所处的数据库

show collections 显示数据库中所有的集合

数据库的增删改查(CRUD)操作

向数据库中插入文档

db.集合名.insert(doc) 向集合中插入一个或多个文档,db 即可表示当前数据库,当向集合中插入文档时,若没有给文档指定 _id 属性,则数据库会自动为文档添加 _id,该属性是根据时间戳生成,作为文档的唯一标识,若自己指定 _id 也要确保唯一性

向 test 数据库 stus 集合中插入一个新的对象
use test
db.stus.insert({name:"xxx",age:18,gende:"female",hobby:{movies:['hero','xxxxx']}});
db.stus.insert([
    {name:"xxx",age:18,gende:"female"},
    {name:"xxx1",age:18,gende:"male"}
]);
db.stus.insert({_id:"hello",name:"xxx",age:18,gende:"female"});

db.集合名.insertOne(doc) 插入一个文档对象

db.集合名.insertMany(doc) 插入多个文档对象

查询文档

db.集合名.find({字段名1:值,字段名2:值}) 查询当前集合中的所有符合条件的文档,也可不传参或对象为空表示查询集合中所有文档,返回数组

db.stus.find({_id:"hello"})

db.集合名.find({}).count() 查询所有结果的数量

db.集合名.findOne({字段名1:值,字段名2:值}) 查询当前集合中符合条件的第一个文档,返回的是一个文档对象

MongoDB 支持直接通过内嵌文档的属性进行查询,通过 "xxx.xxx":'xxx' 查询内嵌文档,但属性名必须使用引号,若要查询的是内嵌文档中数组的某个元素也可直接使用 :"xxx" 查询数组中是否存在该元素

查询喜欢 hero 电影的人
db.stus.find({"hobby.movies":"hero"})

查询的相关操作符:

$eq 等于
$gt 大于
$gte 大于等于
$lt 小于
$lte 小于等于
$or:[{},{}] 或
.limit(条数)  设置显示数据的上限
.skip(条数)  跳过指定数量的数据
.skip((页码-1)*每页显示的条数).limit(每页显示的条数)  显示指定数据,顺序无要求,MongoDB 会自动调整 skip 和 limit 位置

db.numbers.find({num:{$gt:500}})  查询大于500的文档
db.numbers.find({num:{$gt:500, $lt:600}})  查询大于500小于600的文档
db.numbers.find({$or:[{num:{$lt:100}},{num:{$gt:200}}]})  查询小于100或大于200的文档
db.numbers.find().limit(10)  查询前10条数据
db.numbers.find().skip(10).limit(10)  查询前11-20条数据

修改文档

db.集合名.update(查询条件,新对象) 默认会使用新对象替换旧对象,并且只会修改第一个符合条件的文档,若需要修改指定的属性而不是替换则需要使用修改操作符,如

$set 来完成修改文档中指定属性
$unset 用来删除文档中指定属性
$push 用于向数组中添加一个新的元素
$addToSet 向数组中添加一个新的元素,若数组中已存在该元素,则不会添加
$inc 在原来的数据上自增

db.集合名.update(查询条件,新对象,{multi:true}) 修改多个符合条件的文档

db.stuts.update(
    {_id:"hello"},
    {
        $set:{
            gender:"女",
            address:"xxxxx"
        }
    }
);

db.stuts.update(
    {_id:"hello"},
    {
        $unset:{
            address:1
        }
    }
);
注意要删除的属性值写啥无所谓

db.stuts.update(
    {name:"xxx"},
    {
        $push:{
            "hobby.movies":"1111"
        }
    }
);

db.emp.updataMany({sal:{$lte:1000}},{$inc:{sal:400}}); 给薪资低于1000的员工增加400元

db.集合名.updateOne(查询条件,新对象) 修改一个符合条件的文档

db.集合名.updateMany(查询条件,新对象) 修改多个符合条件的文档

db.集合名.replaceOne(查询条件,新对象) 替换一个文档

删除

注意:删除属性依然使用 update

db.集合名.remove(查询条件) 根据条件删除符合条件的所有文档,传递条件的方式和 find 一样,若第二个参数传递一个 true 则只会删除第一个符合条件的文档

db.stuts.remove({_id:"hello"},true);

db.集合名.deleteOne(查询条件,新对象)

db.集合名.deleteMany(查询条件,新对象)

db.集合名.remove({}) 清空集合,集合还在,但性能略差(因为先匹配然后一个个删)

db.集合名.drop() 删除集合,若数据库中只有一个集合,则删除该集合后数据库也会自动删除

db.dropDatabase() 删除数据库

3、文档间的关系

一对一(one to one)

MongoDB 中可通过内嵌文档的形式体现一对一的关系

db.wifeAndHusband.insert([
    {
        name:'xx',
        husband:{
            name;'xxx'
        }
    }
])

一对多(one to many)/ 多对一(many to one)

也可通过内嵌文档的形式体现一对多的关系(使用数组)

db.users.insert([
    {
        username:'xx'
    },{
        username:'xx1'
    }
])
db.order.insert({
    list:["aa","bb","cc"],
    user_id:ObjectId("xxxxxxxxxxxxxxxxx")
})
var user_id = db.users.findOne({username:"xx"})._id
db.order.find({user_id:user_id});

多对多(many to many)

db.teachers.insert([
    {name:'aaa'},
    {name:'bbb'},
    {name:'ccc'},
])
db.stus.insert([
    {
        name:'ddd',
        teach_ids:[
            ObjectId("xxxxxxxxxxxx"),
            ObjectId("xxxxxxxxxxxx")
        ]
    },
    {
        name:'eee',
        teach_ids:[
            ObjectId("xxxxxxxxxxxx"),
            ObjectId("xxxxxxxxxxxx")
        ]
    }
])

4、sort

查询文档时默认是按照 _id 的值进行排列(升序)

sort({对象}) 可以用来指定文档的排序规则,需要传递一个对象来指定排序规则,1 表示升序,-1 表示降序

db.emp.find({}).sort({sal:1});  按 sal 字段升序排列
db.emp.find({}).sort({sal:1,empno:-1});  先按 sal 字段升序再按 empno 降序排列

limit、skip、sort 可以以任意顺序进行调用,都会自动先调用 sort

5、投影

在查询时可以在第二个参数设置查询结果的投影,默认都会显示 _id

db.emp.find({},{ename:1})  会显示 _id 和 ename
db.emp.find({},{ename:1, _id:0})  只显示 ename
db.emp.find({},{ename:1, _id:0, sal:1})  显示 ename 和 sal

三、Mongoose

1、介绍

之前都是通过 shell 来完成对数据库的操作,在开发中大部分时候都需要通过程序来完成对数据库的操作

Mongoose 是一个 Node 中的模块,让我们可以通过 Node 来操作 MongoDB 数据库

Mongoose 是一个对象文档模型(ODM)库,Node 原生的 MongoDB 模块也可以操作 MongoDB 数据库,Mongoose 对 Node 原生的 MongoDB 模块进行进一步优化封装,并提供了更多的功能

在大多数情况下,它被用来把结构化的模式应用到一个 MongoDB 集合,并提供了验证和类型转换等好处

2、Mongoose 优点

(1)可以为文档创建一个模式结构(Schema,即约束),如必须要几个字段、字段类型等

(2)可以对模型中的对象/文档进行验证(因为有约束)

(3)数据可通过类型转换转换为对象模型(因为有约束)

(4)可使用中间件来应用业务逻辑挂钩

(5)比 Node 原生的 MongoDB 驱动更容易

3、安装 Mongoose

(1)下载安装 Mongoose

npm -i mongoose --save

(2)项目中引入 mongoose

const mongoose = require("mongoose");

(3)连接 MongoDB 数据库,可在官网查看方法

mongoose.connect('mongodb://数据库ip地址:端口号/数据库名');  端口号默认是 27017

在 mongoose 对象中有个属性 connection,该对象表示的就是数据库连接,通过监该对象的状态可以来

一旦连接了 MongoDB 数据库后,底层的 Connection 对象就可以通过 mongoose 模块的 connection 属性来访问

connection 对象就是数据库连接的抽象,它提供了对象连接、底层的 Db 对象和表示结合的 Model 对象的访问

并可对 connection 对象上的事件进行监听,来获悉数据库连接的开始与断开,如通过 open 和 close 事件来监控连接的打开和关闭

数据库连接成功的事件
mongoose.connection.once("open",function(){});
数据库断开的事件
mongoose.connection.once("close",function(){});

(4)断开连接(一般不需要)

mongoose.disconnect();

MongoDB 数据库一般情况下只需连接一次,连接一次后,除非项目停止服务器关闭,否则连接一般不会断开

4、Mongoose 中新的对象

(1)Schema(模式对象)

Schema 对象定义约束了数据库中的文档结构,如文档中有几个字段,哪些必须哪些可选等

(2)Model

Model 对象作为集合中的所有文档的表示,相当于 MongoDB 数据库中的集合 collection

通过 Schema 来创建 Model,Model 代表的是数据库中的集合,通过 Model 才能对数据库进行操作

mongoose.model(要映射的集合名, 模式)

注意:mongoose 会自动将集合名变成复数,即“要映射的集合名”中若为单数,在数据库中创建的集合会自动变为复数

(3)Document

Document 表示集合中的具体文档,相当于集合中的一个具体的文档

模型.create(文档对象,function(err){})

注意:三者有顺序要求,先有 Schema 再有 Model 再有 Document

Model 的方法

有了 Model 就可以对数据库进行增删改查操作

添加

Model.create(doc(s), [callback]) 用创建一个或多个文档并添加到数据库中

参数:
    -doc(s) 可以是一个文档对象,也可以是一个文档对象的数组
    -callback 当操作完成后调用的回调函数

查询

Model.find(conditions, [projection], [options], [callback]) 查询所有符合条件的文档,返回数组

Model.findById(id, [projection], [options], [callback]) 根据文档的 id 属性查询文档,返回一个具体的文档对象

Model.findOne([conditions], [projection], [options], [callback]) 查询符合条件的第一个文档,返回一个具体的文档对象

参数
    -conditions 查询的条件
    -projection 投影(需要获取到的字段)
        -例:显示字段1,不显示字段2
        -方式一:{字段1:1,字段2;0}
        -方式二:"字段1 -字段2"
    -options 查询选项(skip、limit)
    -callback 回调函数,查询结果通过回调函数返回,一般回调函数必须传,若不传就不会查询。回调函数的第一个参数是错误,第二个参数就是查询到的数据

find 查询返回的结果就是 Document 文档对象,Document 对象就是 Model 的实例

修改

Model.update(conditions, doc, [options], [callback]) 修改一个或多个文档

Model.updateMany(conditions, doc, [options], [callback])

Model.updateOne(conditions, doc, [options], [callback])

Model.replaceOne(conditions, doc, [options], [callback])

参数:
    -conditions 查询条件
    -doc 修改后的对象
    -options 配置参数,如 {multi:true} 一次修改多个
    -callback 回调函数

删除

Model.remove(conditions, [callback])

Model.deleteMany(conditions, [callback])

Model.deleteOne(conditions, [callback])

其他

Model.count(conditions, [callback]) 统计文档数量

示例

//连接数据库
const mongoose = require("mongoose");
mongoose.connect('mongodb://127.0.0.1:27017/test');

var Schema = mongoose.Schema;
//创建 Schema(模式)对象
var stuSchema = new Schema({
  name:  Number,
  gender: {
        type:String,
        default:"female"
    },
  address: String,
});
//通过 Schema 来创建 Model
var StuModel = mongoose.model('students', stuSchema);
//向数据库中插入一个文档
StuModel.create({
    name:"xxx",
    age:18,
    gender:"male",
    address:"ddddd"
},function(err){
    if(!err){
        console.log("插入成功")
    }
});
//查询
StuModel.find({name:'xxx'},"name -_id", {skip:2,limit:!},function(err,docs){  
//"name -_id"相当于 {name:1,_id:0} ,表示查询 name,不显示 _id(默认会显示 _id)
    if(!err){
        console.log(docs);
    }
});
StuModel.findById("_id的值",function(err,doc){  
    if(!err){
        console.log(doc);
    }
});
StuModel.findOne({},function(err,doc){  
    if(!err){
        console.log(doc);
    }
});
//修改
StuModel.update({name:'xxx'},{$set:{age:20}},{multi:true},function(){
    if(!err){
        console.log("修改成功");
    }
})
//删除
StuModel.remove({name:'xxx'},function(err){
    if(!err){
        console.log("删除成功");
    }
})
//统计文档数量
StuModel.count({},function(err,count){
    if(!err){
        console.log(count);
    }
})

Document 中的方法

Document 和集合中的文档一一对应,Document 是 Model 的实例,通过 Model 查询到的结果都是 Document

因此可以通过 Model 的构造函数 new Model对象 来创建一个 Document 对象

Model#save([options], [options.safe], [options.validateBeforeSave], [fn]) 将文档插入数据库中

Document对象.update(修改内容,[options],[callback]) 修改对象

Document对象.remove([callback]) 删除对象

Document对象.get(属性)Document对象.属性 获取文档中的指定属性值

Document对象.set(属性名,新属性值) 设置文档中的指定属性值

Document对象.id 获取文档的 _id 属性值

Document对象.equal(doc) 比较两个文档是否相同

Document对象.isNew 是否为一个新的文档,即是否存入,是布尔值

isInit(path) 属性是否初始化

toJSON() 转换为一个 JSON 对象

toObject() 将 Document 对象转换为一个普通的 JS 对象,转换后所有的 Document 对象的方法或属性都不能使用了

//创建一个 Document,但并没有插入数据库中
var stu = new StuModel({
    name:"xxx1",
    age:12,
    gender:"female",
    address:"ddddd"
})
//将上面的文档插入数据库中
stu.save(function(err){
    if(!err){
        console.log("保存成功")
    }
})
//
StuModel.findOne({},function(err,doc){  
    if(!err){
        //修改方式一
        doc.update({$set:{age:28}},function(err){
            if(!err){
                console.log("修改成功")
            }
        })
        //修改方式二
        doc.age = 18;
        doc.save();
        //删除
        doc.remove(function(err){
            if(!err){
                console.log("删除成功")
            }
        }
        console.log(doc.get("age"));  //相当于doc.age
        doc.set("name","aaa");  //相当于doc.name="aaa"
        doc.save();
        console.log(doc.id);  //相当于doc._id
    }
});

小结

向数据库中添加数据可以使用 Model.createModel#save(# 表示是 Model 实例对象的方法)

5、Mongoose 模块化

(1)定义一个模块(如 conn_mongo.js),用来连接 MongoDB 数据库

var mongoose = require("mongoose");
mongoose.connect("mongodb://127.0.0.1/test");
mongoose.connection.once("open",function(){
    console.log("数据库连接成功");
});

(2)定义模型对象(如 student.js)

var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var stuSchema = new Schema({
  name:  Number,
  gender: {
        type:String,
        default:"female"
    },
  address: String,
});
//定义模型
var StuModel = mongoose.model('students', stuSchema);
//暴露方式一
exports.model = StuModel;
//暴露方式二:module.exports = StuModel;

(3)使用

require("./conn_mongo");
var Student = require("./student").model; //使用方式一暴露时
//使用方式一暴露时:var Student = require("./student");
//两种方式引入后,Student 就是表示模型对象

其他

1、数据库相关操作速度较慢,应尽量少用数据库操作相关语句,在操作数据库前将数据整理好,如插入 20000 条数据

方式一:

for(var i=1; i<=20000; i++){
    db.numbers.insert({num:i});
}
执行 7.2s

方式二:

var arr = [];
for(var i=1; i<=20000; i++){
    arr.push({num:i});
}
db.numbers.insert(arr);
执行 0.4s