MongoDB

127 阅读4分钟

MongoDB

  • 介于关系型数据库与非关系型数据库之间

适用场景

  • 处理大量低价值的数据而且对性能有很高的要求
  • 可以用mongoDB来实现对数据的缓存
  • 可以搭建集群环境,实现存储数据的横向扩展

mac安装

  • brew tap mongodb/brew
  • brew update
  • brew install mongodb-community@6.0

常用命令

查看配置信息

  • vim /usr/local/etc/mongod.conf
systemLog:
  destination: file //适用文件形式存储
  path: /usr/local/var/log/mongodb/mongo.log //日志文件地址
  logAppend: true //使用追加形式填写日志
storage:
  dbPath: /usr/local/var/mongodb  //mongo所在地址
net:
  bindIp: 127.0.0.1, ::1
  ipv6: true

启动

  • 二选一
    • mongod --config /usr/local/etc/mongod.conf
    • brew services start mongodb/brew/mongodb-community
  • which mongod 查看是否启动成功

连接

  • mongosh
    • 默认名称是test,连接的mongo默认端口是27017
    • 连接成功后进入可交互命令窗口,会自带语法高亮,且支持js运行

命令

查询数据库

  • show dbs
  • 默认三个库
admin   40.00 KiB
config  60.00 KiB
local   40.00 KiB

使用/切换数据库

  • use 库名

查询当前所在数据库

  • db
    • db代表当前数据库实例

删除库

  • 进入库
  • db.dropDatabase()
  • 会返回 {ok:1,dropped:库名}

查询表

  • show collections
插入数据
  • db.student.insert(数据)
    • 将数据插入到student表中
    • 会自动创建成功
local> db.student.insert({name:'cui'})

DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite.
{
  acknowledged: true,
  insertedIds: { '0': ObjectId("62f0f3aaa60c6a176ea71ca5") }
}
查询表数据
  • db.表名.find()
  • 默认是查询全部
  • find({})等同于全部,第一个参数可以进行条件查询
  • 第二个参数可以进行字段筛选,0 为显示,1位隐藏, 0和1不能同时存在
// 查询那么为cui的数据,且结果不要name属性,只给出age
// 第二个参数不填,默认返回全部
// 场景:用户根据姓名查询年龄,但库里有密码,我肯定不希望把密码给它,所以只给年龄即可
db.student.find({name:'cui'},{age:1});

// 错误示范
db.student.find({},{name:0, age:1})

//查询多条,使用$or
// 返回name为cui1或cui2的数据
db.student.find({$or:[{name:'cui0'},{name:'cui1'}]})

// 范围查询
// age大于7,小于9
db.student.find({age:{$gt:7, $lt:9}})

// $gt 大于  $gte 大于等于
// $lt 小于  $lte 小于等于

// 排除,不等于 $ne
// 获取age不为7的所有值
db.student.find({age:{$ne:7}})

//正则

//匹配以cui开头的name
db.student.find({name:/^cui/})

// 逻辑与,假设address为obj
// 查找address.value为cc且num为1的值

db.student.find({"address.value":"cc", 'address.num': "1"})

// 假设ary为数组,查询某个索引的话与object用法一样
// $all 表示包含,查询ary中包含1,2,5的项
db.student.find({"ary":{$all:[1,2,5]}});

// $size 表示包含,查询length为3的项
db.student.find({"ary":{$size:3}});
修改
  • 想要修改肯定需要先查询,所以update第一个参数与find一致
  • 第二个参如果直接写内容的话,会进行覆盖性修改,会把原来的信息干掉
  • 如果想增加式修改的话,使用$set
  • updateMany是全部,one是单条
db.student.update({"address.value":"cc"}, {$set:{name: "haha"}})
删除
  • 第一个参数也是条件筛选
//删除全部
db.student.deleteMany({})
给表创建用户
  • db.createUser(信息)
    • user:姓名
    • pwd:密码
    • roles:权限
db.createUser({user:'cui',pwd:'csl',roles:[{role:权限,db:库名}]})

开启权限校验

  • 连续两次command+c退出交互窗口
  • vim /usr/local/etc/mongod.conf
// 追加
security:
  authorization: enabled

数据库身份

  • dbOwner 该数据库的所有者,具有该数据库的所有权限
  • dbAdmin 一些数据库对象的管理操作,但没有读写权限
  • userAdmin 为当前用户创建、修改用户和觉得。拥有userAdmin权限的用户可以将该数据库的任意权限赋予任意用户

重启

  • 修改配置文件需重启
  • brew services restart mongodb-community
  • 这个时候再通过mongosh链接数据库,发现没权限了,需要登录

登录

  • db.auth('用户名')
  • 回车,然后输入密码,回车,返回ok代表登录成功

备份数据

  • mongodump --db 数据库 --collection 表名 --out backup 备份数据库

还原

  • mongorestore backup

导出指定格式

  • mongoexport -d 数据库 -c 表 --type 类型(json、csv) -f _id,name -o 导出文件名

可视化工具

  • connect-》new Connection 建立一个新的链接
    • url就是要访问的数据库
    • edit 可编辑信息,如端口号、ip、权限

在node中使用

  • 第三方库 mongoose

初体验

connect

  • 想要在数据库上增删改查,肯定得先连接
    • connect 连接库,两个参数,数据库url,callback
    • callback的第一个参数为err,如果err存在,代表连接失败
const mongoose = require("mongoose");
// 连接web数据库
mongoose.connect("mongodb://127.0.0.1:27017/web", function (err) {
  if (err) return console.log(`异常信息${err}`);

  console.log("连接成功");
});

Schema

  • 对内容进行写入的时候,mongo是比较自由的,那么也会导致大量数据的不规范性
  • 使用Schema描述一个表结构,在存储的时候,会根据结构进行校验
  • 校验不通过,会抛出异常
  • 如果插入的数据中有某个项存在,而描述的表结构中不存在,虽然校验会通过,但不会多余内容不会插入到库中
// 规范化,描述表的结构,可以理解为是ts中的类型校验

const UserSchema = mongoose.Schema({
  name: {
    type: String, //字符串
    require: true, //必填
    unique: true, //唯一标识
    lowercase: true, //小写
    trim: true, //去除空格
  },
  pwd: {
    type: String,
    validate(value) {
      // 校验器,返回true才可以,适用于复杂校验
      return value.length > 0;
    },
  },
  age: {
    type: Number,
    default: 0, //默认0
    min: 0, //最小0
    max: 120, //最大120
  },
  gender: {
    type: Number,
    enum: [0, 1], //值只能是0或1
  },
  address: {
    num: Number,
    value: String,
  },
});

方法拓展,自定义api
  • 比如说想根据name查询,但并没有对应方法可以直接查询,那么我们可以将方法封装在Schema基类上
//原始方法
User.findOne({ name: "xxx" });

// 封装方法
UserSchema.statics.findName = function (username) {
  return this.findOne({ name: username });
};

//调用
User.findName("cc")
plugin
  • 如果扩展的多的话,可以使用plugin
  • 会将this当做第一个参数传递给plugin的callback,第二个参也传过去
// 插件集合
function plugin(User, options) {
  // User = UserSchema
  // options = {xxx: 'xxx'}
  User.statics.findName = function (username) {
    return this.findOne({ name: username });
  };
  ...
}

UserSchema.plugin(plugin, {xxx: 'xxx'})
创建时间、修改时间
  • 两种方法:
    • 直接写一个默认值
    • 调用库本身能力
// 方法1,自己写进入
const UserSchema = mongoose.Schema({
  birtyday: {
    type: Date,
    // 创建时间,这里now方法不要加(),因为我们想在插入的时候才执行,加了()就变为初始化的时候执行,那就失去意义了
    default: Date.now,
  },
});
// 方法2
const UserSchema = mongoose.Schema(
  {
   ....
  },
  // 添加第二个obj
  // 都要的话,可以简写成timestamps:true
  {
    // 创建用户会自动添加
    timestamps: {
      createAt: true,
      updateAt: false, // 不想要的话,就给false
    },
  }
);

model

  • 创建并连接表
  • 三个参数:
    • 表名,默认会转小写,且追加+s
    • Schema表结构
    • 自定义表名,可选参数,强制覆盖第一个参数
  • 返回一个表实例,可以对当前连接表进行增持删改查
// 会创建一个hahas表
const User = mongoose.model("haha", UserSchema);
// 会创建一个hahas表
const User = mongoose.model("Haha", UserSchema);
// 以第三个参为准,表名为haha
const User = mongoose.model("Haha", UserSchema, "haha");
create
  • 创建某个或多个数据
    • 单条数据就用object
    • 多条就用array
  • 支持promise
  • 创建成功,then第一个参为入库具体参数
  • 失败,catch为异常
// 单条
(async () => {
  let r = await User.create({
    name: "cc",
    pwd: "sss",
    age: 120,
    a: 1, //我在这写了a,但描述的骨架没没有a,虽然校验通过了,但不会被存储到数据库
    gender: 1,
    address: { num: 10, value: "地址" },
  });
  console.log(r);
  //r是存储到数据库内的结构
  /**
   * {
        name: 'shou',
        pwd: 'sss',
        age: 120,
        gender: 1,
        address: { num: 10, value: '地址' },
        _id: new ObjectId("62f1129be41efa4de8dbc174"),
        __v: 0
    }   
   */
})();

// 多条
(async () => {
  let arr = [];
  for (let i = 0; i < 10; i++) {
    arr.push({
      name: "cc" + i,
      pwd: "sss",
      age: i,
      gender: 1,
      address: { num: 10, value: "地址" },
    });
  }
  let r = await User.create(arr);
  console.log(r);// 多条的话,返回的是[{},{}]
})();

deleteMany
  • 删除匹配到的全部数据
  • {} 清空表结构
User.deleteMany({});
find
  • 查找,用法与mongo原生语句一致
  • 实例上有一个countDocuments方法,可以拿到本次获取数据总数
(async () => {
  let r = await User.find();
  console.log(r); // [{}.....]
  let total = await User.find().countDocuments();
  console.log(total); // 10
})();
分页
  • find的高级应用
  • limit设置每页多少条
  • skip设置跳过多少条,内部会自动先执行skip
(async () => {
  const limit = 3;
  const curPage = 2; // 当前为第2页
  let r = await User.find()
    .limit(limit)
    .skip((curPage - 1) * limit);// 第一页数据 0-2,第二页3-5,那跳过3条:0-2
  console.log(r);
})();

sort
  • 排序
  • -1为倒序,从大到小
  • 1为正序,从小到大
  • 如果与skip和limit一同执行,会先执行sort,然后skip,最后limit
  let r = await User.find().sort({ age: -1 });
  console.log(r);
findById
  • 根据id查询数据
  • 语法糖
User.findById("62f121b29ed01ff4f2865057");
创建article
  • 创建一个文章表
const mongoose = require("mongoose");

const ArticleSchema = mongoose.Schema(
  {
    title: String, // 标题
    content: String, // 内容
    user_id: {
      type: mongoose.SchemaTypes.ObjectId,
    }, // 用户
  },
  {
    timestamps: true,
  }
);

module.exports = mongoose.model("Article", ArticleSchema, "article");
  • 通过new来创建数据,但new一次性只能放一条数据,跟create一样
  • 然后调用save方法保存到表中
(async () => {
  let user = await User.findById("62f121b29ed01ff4f2865057");
  let article = await new Article({
    title: "node",
    content: "内容2",
    user_id: user._id,
  }).save();
  console.log(article);
})();
联表查询
  • 根据文章id,拿到对应的作者信息
  • 不推荐

(async () => {
  let article = await Article.findById("62f251b287e45b3c3850931a");
  let user = await User.findById(article.user_id);
  console.log(user);
})();
populate
  • 第一个参为根据某个值查询
  • 第二个参等同于find第二个参,作用为筛选出某个字段
  • 默认会把查询到的参数塞到第一个参数中返回,默认返回全部
  • 步骤
    • 在创建Schema的时候加上ref,对应到User上
    • 那么查询的时候,调用populate填写对应字段会自动关联
const ArticleSchema = mongoose.Schema(
  {
    ...
    user_id: {
      ref: "User",
      type: mongoose.SchemaTypes.ObjectId,
    }, // 用户
  },
  ...
);

(async () => {
  let article = await Article.findById("62f251b287e45b3c3850931a").populate(
    "user_id"
  );
  console.log(article);
  /**
   {
    _id: new ObjectId("62f251b287e45b3c3850931a"),
    title: 'node',
    content: '内容',
    user_id: {
      address: { num: 10, value: '地址' },
      _id: new ObjectId("62f121b29ed01ff4f2865057"),
      name: 'cc4',
      pwd: 'sss',
      age: 4,
      gender: 1,
      createdAt: 2022-08-08T14:46:10.338Z,
      updatedAt: 2022-08-08T14:46:10.338Z,
      __v: 0
    },
    createdAt: 2022-08-09T12:23:14.036Z,
    updatedAt: 2022-08-09T12:23:14.036Z,
    __v: 0
  }
   */
})();

// 只找name
// Article.findById("62f251b287e45b3c3850931a").populate("user_id",{ name: 1 });

aggregate

  • 聚合查询,推荐
  • 不用再加ref了
  • 还是上述例子
  • 多种功能集成到一个方法里
  • 查询多个表的话,加多个$lookup就可以了
 let article = await Article.aggregate([
    {
      $lookup: {// 查询领一张表的规则
        from: "users", // 另一张表的表名
        localField: "user_id", // 根据哪个字段查询,本地字段
        foreignField: "_id", // 对应的是
        as: "user", // 放到哪个字段上
      },
    },
    {
      $match: {// 匹配当前表
        _id: mongoose.Types.ObjectId("62f251b287e45b3c3850931a"),
      },
    },
  ]);
  console.log(article);
示例
  • 给article的类型声明添加一个cate_id,让其可以关联类型表
  • 创建cate类型表
// article
const ArticleSchema = mongoose.Schema(
  {
    ...
    cate_id: {
      // 分类id
      type: mongoose.SchemaTypes.ObjectId,
    }
  }
  ...
);


/**
 * cate分类表
 */
const mongoose = require("mongoose");

const CateSchema = mongoose.Schema(
  {
    title: String, // 标题
  },
  {
    timestamps: true,
  }
);

module.exports = mongoose.model("Cate", CateSchema, "cate");

const Cate = require("./module/cate");

// 插入两条类型数据
(async () => {
  await Cate.create([{ title: "a" }, { title: "b" }]);
})();

// 更新一个文章,给其添加类型id
(async () => {
  let r = await Article.findByIdAndUpdate("62f251b287e45b3c3850931a", {
    cate_id: "62f25d0ce1c899f1808a22aa",
  });
  console.log(r);
})();

// 查询
(async () => {
  let article = await Article.aggregate([
    {
      $lookup: {
        // 查询领一张表的规则
        from: "users", // 另一张表的表名
        localField: "user_id", // 根据哪个字段查询,本地字段
        foreignField: "_id", // 对应的是
        as: "user",
      },
    },
    {
      $lookup: {
        // 查询领一张表的规则
        from: "cate", // 另一张表的表名
        localField: "cate_id", // 根据哪个字段查询,本地字段
        foreignField: "_id", // 对应的是
        as: "cate",
      },
    },
    {
      $match: {//match为{}会拿到所有数据
        // 匹配当前表中_id为62f251b287e45b3c3850931a的数据
        _id: mongoose.Types.ObjectId("62f251b287e45b3c3850931a"),
      },
    },
    {
      $project: {
        user: {
          name: 1, // user表只要name
        },
        cate: {
          title: 1, // cate表只要title
        },
      },
    },
  ]);
  console.log(article[0].user);
})();

  • 分组求和
(async () => {
  let article = await User.aggregate([
    {
      $match: {},
    },
    {
      $group: {
        // 根据name进行分组,求分组内age总数
        _id: "$name",
        age: { $sum: "$age" },
      },
    },
  ]);
})();
  • 分页
//跟上面一样,只不过写方法里面了
{
      $limit: 3,
},
{
      $skip: 5,
},