MongoDB

NoSQL => 文档数据库 MongoDB => 27017

MongoDB
MongoDB

初识

MongoDB@3.2 后存在较大改动,使用时推荐官方中文文档

安装 MongoDB 5.0 社区版(先决条件:Xcode 命令行工具、Homebrew):

  • 下载 MongoDB 和数据库工具并安装
# tap 命令允许 Homebrew 进入另一个公式存储库. 完成此操作则扩展可安装软件的选项
brew tap mongodb/brew
# 安装
brew install mongodb-community@5.0

安装会在以下指定位置创建文件和目录,具体取决于 Apple 硬件:

英特尔处理器 M1处理器
配置文件 /usr/local/etc/mongod.conf /opt/homebrew/etc/mongod.conf
log directory /usr/local/var/log/mongodb /opt/homebrew/var/log/mongodb
data directory /usr/local/var/mongodb /opt/homebrew/var/mongodb
  • 基本操作
# 将 MongoDB(即mongod进程)作为 macOS 服务运行|停止
brew services start|stop mongodb-community@5.0
# 验证是否正在运行
brew services list
# 连接和使用 MongoDB
mongosh
# 展示数据库
show databases; # 或者 show dbs;
# 显示当前数据库
db
# 查看版本信息
db.version();
# 选中数据库
use $yourdatabase; # 如果没有会隐式创建
# 查看表(集合)
show tables; # 因为并非多行多列,叫表不合适
show collections; ✔️
# 创建集合
db.createCollection("necessities"); # { ok: 1 }
# 删除集合
db.collectionName.drop(); # true
# 删除数据库
db.dropDatabase(); # 删除当前选用的数据库
# 插入 JSON 数据
db.necessities.insert({"name":"Dryer","origin":"San Antonio"}); # ❌ DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite. 
# 3.2 版本之后新增了 db.collection.insertOne() 和 db.collection.insertMany()
db.necessities.insertOne({"name":"Dryer","origin":"San Antonio"}); # ✔️
db.necessities.insertMany([{"_id":"612e26f5d5f808a806f499e6","name":"dryer","origin":"San Antonio"},{"_id":"612e27b6d5f808a806f499e8","name":"charger","origin":"San Diego"},{"_id":"612e2e03d5f808a806f499e9","name":"conditioner","origin":"Seattle"},{"_id":"612e3737d5f808a806f499ea","name":"waitforremove","origin":"zsxzy"}]);

insertMany

# 查询集合数据 —— $currentDB.currentCollection.find(query, projection) 
# query(可选):使用查询操作符指定查询条件;projection(可选):使用投影操作符指定返回的键
db.necessities.findOne()|find()|find().count(); # 查看一个文档|最多20个匹配的文档|查看集合中的文档数量
# 移除集合中的数据
db.necessities.deleteOne({"name":"waitforremove"});
# 更新集合数据
# 如果第二个参数未设置 $set 则会报错 TypeError: Update document requires atomic operators
db.necessities.updateOne({"_id":"612e3737d5f808a806f499ea","name":"waitforremove","origin":"zsxzy"},{$set:{"name":"toytoytoy"}});

查询将会返回一个数据库游标,游标只会在需要时才将需要的文档批量返回;游标 (cursor) 不是查询结果,而是查询的返回资源或者接口。

通常文档只会部分更新。可以使用原子性的更新修改器(update modifier),指定对文档中的某些字段进行更新。更新修改器是种特殊的键,用来指定复杂的更新操作,比如修改、增加或者删除键,还可能是操作数组或者内嵌文档。

假设要在一个集合中放置网站的分析数据,只要有人访问页面,就增加计数器。可以使用更新修改器原子性地完成这个增加。每个 URL 及对应的访问次数都以如下方式存储在文档中:

{
    "_id" : ObjectId("4 字节的时间戳 5 字节的随机值 3 字节递增计数器"),
    "url" : "www.zstheyi.com",
    "pageviews" : 422
}

每次有人访问页面,就通过URL找到该页面,并用 $inc 修改器增加 pageviews 的值。使用修改器时,_id 的值不能改变。

$set 用来指定一个字段的值。如果这个字段不存在,则创建它。常用于更新或者增加文档。删除可以用 $unset 完全删除键。在数组修改器中,$push 会向已有的数组末尾加入元素,要是没有就创建一个新的数组,也可以与 $each 组合一次添加多个值。为了保证数组内元素不重复,$addToSet 与 $ne 可以实现集合数据。$pop 这个修改器根据传入的 key 从数组任何一端删除元素。$pull 会将所有匹配的文档删除,而不是只删除一个。

# 条件操作符 —— $gt (>), $gte (>=), $in (包含), $lt (<), $lte (<=), $ne (!=), $nin(不包含)、$and (&&), $nor(不符合), $not(不包含), $or (||)
db.necessities.find({"name":"dryer","origin":"San Diego"}).pretty(); 
db.necessities.find({$or:[{"name":"dryer"},{"origin":"San Diego"}]}).pretty(); # OR 条件
db.necessities.find({"price":{$gt:"1"}})|find({"_id":{$gt:"1"}})

# $type键类型操作符
db.necessities.find({"name":{$type:"string"}}) # 筛选出指定类型键的数据

# Limit 与 Skip 方法
db.necessities.find().limit(2) # 显示两条文档
db.necessities.find().skip(4) # 跳过指定数量的数据

# 排序操作 —— db.COLLECTION_NAME.find().sort({KEY:1})
db.necessities.find().sort({"price":"1"}) # 1为由小到大,-1为由大到小

# 索引操作
db.necessities.createIndex({"price":1}) # 创建索引
db.necessities.getIndexes() # 查看索引
db.necessities.totalIndexSize() # 查看集合索引大小
db.necessities.dropIndexes() # 删除集合所有索引
db.necessities.dropIndex("price") # 删除集合指定索引

# 聚合(aggregate) —— 主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果
db.necessities.aggregate([{$group:{_id:"$origin",total:{$sum:"$price"}}}]) # 通过字段 origin 字段对数据进行分组,并计算 price 字段相同值的总和

插入文档数据不能使用 db.$yourcollection.insert() 进行操作,而是需要采取 insertOne() 或者 insertMany()。更新文档数据时,使用 replaceOne(),只能替换整个文档;而 updateOne() 则允许更新字段,同时还可以访问 update operators 对文档进行可靠更新。移除集合中的数据不再使用 remove(),而改用 deleteOne, deleteMany, findOneAndDelete 或 bulkWrite。

  • 在 Navicat Premium 连接 MongoDB

在 connection 选择对应数据库后,按照默认规则连接即可,连接的默认端口为 27017。在工具栏的 View 选择 Show Hidden Items 可展示隐藏初始的数据库:admin、config、local。

Mongoose

Mongoose 是一个 NodeJS 专门用于操作 mongodb 数据库的模块,也是一个在异步环境中工作,支持 promise 和异步回调的对象文档模型库。Mongoose 可以为文档创建一个模式结构 (Schema|约束),并对模型中的对象/文档进行验证,数据可以通过类型转换为对象模型,以及可以使用中间件来应用业务逻辑挂钩,比较原生的 MongoDB 驱动更容易(进一步封装)。

mongoose对象 作用描述
Schema 模式对象定义约束了数据库文档结构
Model Model 对象作为所有文档的表示,相当于数据库的集合 collection
Document 表示集合中的具体文档,相当于集合中的一个具体文档
# 下载 mongoose 连接数据库
npm init -y
npm i mongoose --save
// 基本连接
// 项目引入mongoose
var mongoose = require('mongoose');
// 连接 mongodb —— 除非项目停止,服务器关闭,否则连接一般不会断开
mongoose.connect('mongodb://localhost:27017/数据库名');
// 监听连接状态 —— mongoose 有一个属性叫做 connection,该对象表示数据库连接
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() { // 数据库连接的事件
  // you're connected!
});
db.once('close', function() { // 数据库断开的事件
  // bye!
});
// 断开数据库连接(一般不需要)
mongoose.disconnect()
// 使用 Schema
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/mongoose_sgg');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
  console.log("you're connected!");
});
db.once('close', function() {
  console.log("bye!");
});
// Schema是构造函数
var Schema = mongoose.Schema;
var stuSchema = new Schema({ // 创建对数据库的约束
    name: String,
    age: Number,
    gender:{
        type: String, // Boolean限制太多,开发不常用
        default: "female"
    },
    address: String
})
// 通过 Schema 创建 Model 数据库中的集合
// mongoose.model(modelName, schema);
// 和数据库的modelName的集合映射,通过stuSchema进行约束
var StuModel = mongoose.model("student", stuSchema); // mongoose会自动将集合名变为复数
// 数据库插入文档
StuModel.create({
    name: "bjj",
    age: 17,
    address: "whitebonehole"
}, function(err){
    if(!err){
        console.log("插入成功");
    }
})
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/mongoose_sgg');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
  console.log("you're connected!");
});
db.once('close', function() {
  console.log("bye!");
});
var Schema = mongoose.Schema;
var stuSchema = new Schema({
    name: String,
    age: Number,
    gender:{
        type: String,
        default: "female"
    },
    address: String
})
var StuModel = mongoose.model("student", stuSchema);
// 有了文档对象就可以对文档进行增删改差
// Model.create(docs,[callback])
// StuModel.create([
//     {
//         name: "zbj",
//         age: 28,
//         gender: "male",
//         address: "gaolaozhuang"
//     },
//     {
//         name: "ts",
//         age: 15,
//         gender: "male",
//         address: "datang"
//     }
// ], function(err, jellybean, snickers){ // 返回成功状态以及插入结果
//     if(!err){
//         console.log("插入成功");
//     }
// })
// projection-投影(需要获取的字段)、options-查询选项(skip、limit)、callback-查询结果通过回调函数返回
// Model.find(conditions, [projection], [options], [callback]); —— 查询符合条件文档,总会返回数组
// Model.findById(conditions, [projection], [options], [callback]);
// Model.findOne(conditions, [projection], [options], [callback]);
StuModel.find({name:"ts"}, {name:1, _id:0, address:1}, function(err,docs){ // 显示name不显示_id => 可以直接写字符串,不需要的前面加上-
    if(!err){
        console.log(docs); // 返回的是数组 [ { name: 'ts', address: 'datang' } ]
        console.log(docs[0].name); // ts
    }
})
StuModel.find({}, "name -_id", {skip:1}, function(err,docs){ // 显示name不显示_id => 可以直接写字符串,不需要的前面加上-
    if(!err){
        console.log(docs); // [ { name: 'bjj' }, { name: 'zbj' }, { name: 'ts' } ]
    }
})
StuModel.findById("613397ae66319ccd3dbb0c05", function(err,doc){ // 显示name不显示_id => 可以直接写字符串,不需要的前面加上-
    if(!err){
        console.log(doc); // 指定_id的对象
    }
})
StuModel.findOne({}, "name -_id", {skip:1}, function(err,doc){ // 显示name不显示_id => 可以直接写字符串,不需要的前面加上-
    if(!err){
        console.log(doc); // { name: 'bjj' }
        // Document对象是Model的实例
        console.log(doc instanceof StuModel) // true
    }
})
// doc为修改后的对象
// Model.update(conditions, doc, [options], [callback]);
// Model.updateMany(conditions, doc, [options], [callback]);
// Model.updateOne(conditions, doc, [options], [callback]);
// 修改ts年龄为20
StuModel.updateOne({name: 'ts'}, {$set:{age:20}}, function(err){
    if(!err) {
        console.log("修改成功");
    }
})
// Model.remove(conditions, [callback]); 一个已弃用的函数,已被 deleteOne() 删除单个文档和 deleteMany() 删除多个文档取代
// Model.deleteOne(conditions, [callback]); 用于删除单个文档
// Model.deleteMany(conditions, [callback]); 在删除后返回已删除的文档以防在删除操作后需要其内容
StuModel.remove({name: 'zbj'},function(err){
    if(!err) {
        console.log("删除成功");
    }
})
// Module.count(condition, [callback])
StuModel.count({}, function(err, count){
    if(!err){
        console.log(count); // 效果比find得出数组后length好,不必查找每个文档
    }
})
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/mongoose_sgg');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
  console.log("you're connected!");
});
db.once('close', function() {
  console.log("bye!");
});
var Schema = mongoose.Schema;
var stuSchema = new Schema({
    name: String,
    age: Number,
    gender:{
        type: String,
        default: "female"
    },
    address: String
})
var StuModel = mongoose.model("student", stuSchema);
// Document和集合中文档一一对应,Document是Model实例
// 通过Model查询结果都是Document
// 创建一个document
// var stu = new StuModel({
//     name:"zxxz",
//     age:19,
//     gender: "female",
//     address: "tiangong"
// });
// stu.save(function(err){
//     if(!err){
//         console.log("保存成功"); // Saves this document.
//     }
// })
StuModel.findOne({},function(err, doc){
    // 方式一
    // if(!err){
    //     // update(update,[option],[callback])
    //     doc.updateOne({$set:{age:20}},function(err){
    //         if(!err){
    //             console.log("修改成功");
    //         }
    //     })
    // }
    // 方式二
    // doc.age = 19;
    // doc.save();
    // doc.remove(function(err) {
    //     if(!err){
    //         console.log("大师兄再见");
    //     }
    // })
    // get(name) 获取文档中指定属性值
    // console.log(doc.get("name")); // 等价于 console.log(doc.name);
    // set(name, value) 设置文档指定属性值
    // doc.set("name","nicebjj") // 数据库不变,因为没有调用save 等价于 doc.name="hahaha"
    // console.log(doc);
    // toJSON() 转换为一个JSON对象
    // toObject() 文档对象转化为一个普通的js对象 —— 部分方法使用不了
})

MongoDB NodeJS Driver

Tips

MongoDB 自增 ID

MongoDB 没有像 SQL 一样有自动增长的属性,MongoDB 的 _id 是系统自动生成的 12 字节唯一标识。为实现 ObjectId 自动增长,需要辅助函数进行实现。

  • 使用指定集合 => 以 channels 为例
db.createCollection("channels")
  • 向集合中插入以下文档,使用 cid 作为 key
db.counters.insertOne({_id:"cid","sequence_value":0})
  • 创建函数 getNextSequenceValue 作为序列名的输入
function getNextSequenceValue( sequenceName ) {
  var sequenceDocument = db.counters.findAndModify(
    {
      query:{_id: sequenceName },
      update: {$inc:{sequence_value:1}},
      new:true
    });
  return sequenceDocument.sequence_value;
}
  • 调用 getNextSequenceValue 函数设置文档 _id 自动为返回的序列值
db.channels.insertOne({"_id":getNextSequenceValue("cid"),"channels_name":"推荐"})
{ acknowledged: true, insertedId: 1 }
db.channels.insertOne({"_id":getNextSequenceValue("cid"),"channels_name":"c++"})
{ acknowledged: true, insertedId: 2 }

结束

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处 zairesinatra's blog