MongoDB、NestJS 集成 @nestjs/mongoose

MongoDB

目录


基础概念

什么是 MongoDB

  • NoSQL 数据库:存储键值对数据
  • BSON 格式:Binary JSON,类似 JSON 的二进制存储格式
  • 文档数据库:数据以文档形式存储,类似 JSON 对象

术语对比

关系数据库 MongoDB 说明
database database 数据库
table collection 集合
row document 文档
column field 字段
primary key _id 主键(自动生成)

数据示例

javascript 复制代码
{
    "_id" : ObjectId("5c89f787ca6e4e3ac1ecabkk"),
    "_plat" : "test_plat0",
    "update_time" : ISODate("2019-06-03T15:00:42.142Z"),
    "create_time" : ISODate("2019-03-14T14:41:11.217Z"),
    "creator" : "test_user",
    "admin" : [ 
        "admin1", 
        "admin2"
    ],
    "ops" : [ 
        "ops1"
    ],
    "labels" : {
        "department" : "departmentA",
        "main_class" : "mainClassA"
    }
}

数据格式规则

  • 键不能含有 \0(空字符)。这个字符用来表示键的结尾。
  • .$ 有特别的意义,只有在特定环境下才能使用。
  • 以下划线_开头的键是保留的(不是严格要求的)。

安装配置

快速安装

Windows
  1. 下载 MongoDB Community Server
  2. 运行安装程序
  3. 安装 MongoDB Compass(图形化管理工具)
Linux (Ubuntu/Debian)
bash 复制代码
wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
macOS
bash 复制代码
brew tap mongodb/brew
brew install mongodb-community

启动服务

bash 复制代码
# 启动服务
sudo systemctl start mongod

# 设置开机自启
sudo systemctl enable mongod

# 检查状态
sudo systemctl status mongod

连接数据库

bash 复制代码
# 本地连接
mongo

# 指定主机端口
mongo --host localhost --port 27017

# 连接指定数据库
mongo mydb

基础操作

数据库操作

javascript 复制代码
// 显示所有数据库
show dbs

// 切换/创建数据库
use mydb

// 显示当前数据库
db

// 删除当前数据库
db.dropDatabase()

集合操作

javascript 复制代码
// 显示所有集合
show collections

// 创建集合
db.createCollection("users")

// 创建带选项的集合
db.createCollection("logs", {
  capped: true,      // 固定大小集合
  size: 100000,      // 最大字节数
  max: 1000          // 最大文档数
})

// 删除集合
db.users.drop()

数据插入

插入数据有4种方法:insert , insertOne , insertMany , save

insert

insert 可以插入一条或多条数据

javascript 复制代码
// 插入一条数据
db.col.insert({"name":"xiaoming"})

// 插入多条数据
db.col.insert([{"name":"xiaoming"}, {"name":"test_user"}])
insertOne

insertOne 只能插入一条数据

javascript 复制代码
// 一条数据
db.col.insertOne({"name":"xiaoming"})
insertMany

insertMany 可以插入一条或多条数据,但是参数**必须都以列表(list - 数组)**的方式

javascript 复制代码
// 插入一条数据
db.col.insertMany([{"name":"xiaoming"}])

// 插入多条数据
db.col.insertMany([{"name":"xiaoming"}, {"name":"test_user"}])
save
  • 如果不指定 _id , save 的功能与insert 一样。
  • 如果指定 _idmongodb就不为该条记录自动生成_id了。

只有 save 可以指定 _id 的值

javascript 复制代码
// 指定id
db.col.save({"_id":ObjectId("5d07461141623d5db6cd4d43"),"name":"xiaoming"})

数据更新

update 方法

update 接收三个参数:

  • query : 查询条件,找到指定的数据(document),对其做更新操作
  • update : 具体的更新操作,数据覆盖,修改
  • options : 可选参数,其余的插入、修改配置项
javascript 复制代码
db.collection.update(
   <query>,   // update的查询条件,类似sql update语句where后面的部分
   <update>,  // update的对象和一些更新的操作符等,也可以理解为sql update语句set后面的
   {
     upsert: <boolean>,  // 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入
     multi: <boolean>,   // 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新
     writeConcern: <document>  // 可选,抛出异常的级别
   }
)
基本更新操作符
javascript 复制代码
// $set - 设置字段值
db.users.update(
  { name: "张三" },
  { $set: { age: 26, city: "上海" } }
)

// $unset - 删除字段
db.users.update(
  { name: "张三" },
  { $unset: { age: "" } }
)

// $inc - 数值增减
db.users.update(
  { name: "张三" },
  { $inc: { age: 1 } }
)
数组操作符
javascript 复制代码
// $push - 向数组添加元素
db.users.update(
  { name: "张三" },
  { $push: { hobbies: "跑步" } }
)

// $pull - 从数组删除元素
db.users.update(
  { name: "张三" },
  { $pull: { hobbies: "跑步" } }
)

// $addToSet - 添加唯一元素(避免重复)
db.users.update(
  { name: "张三" },
  { $addToSet: { hobbies: "游泳" } }
)

// 向数组添加多个元素
db.users.update(
  { name: "张三" },
  { $push: { hobbies: { $each: ["读书", "跑步"] } } }
)
更新选项
javascript 复制代码
// multi: true - 更新多条记录
db.users.update(
  { age: { $lt: 30 } },
  { $set: { category: "young" } },
  { multi: true }
)

// upsert: true - 不存在则插入
db.users.update(
  { name: "新用户" },
  { $set: { age: 25 } },
  { upsert: true }
)
save 方法

save 方法通过传入的 document 来替换已有 document

javascript 复制代码
db.col.save({
   "_id" : ObjectId("5d0751ef41623d5db6cd4d44"), // 指定_id,新的文档会将旧的文档覆盖
   "name" : "xming",
   "class" : "c++",
   "score" : 80
})

数据删除

数据删除可以使用 deleteOnedeleteManyremoveremove 不推荐使用。

javascript 复制代码
// 删除一条记录
db.users.deleteOne({ name: "张三" })

// 删除多条记录
db.users.deleteMany({ age: { $lt: 18 } })

// 删除所有记录
db.users.deleteMany({})

查询操作

基本查询

javascript 复制代码
// 查询所有
db.users.find()

// 查询一条
db.users.findOne({ name: "张三" })

// 美化输出
db.users.find().pretty()

条件操作符

操作符 含义 示例
= 等于 { age: 25 }
$lt 小于 { age: { $lt: 30 } }
$lte 小于等于 { age: { $lte: 30 } }
$gt 大于 { age: { $gt: 18 } }
$gte 大于等于 { age: { $gte: 18 } }
$ne 不等于 { age: { $ne: 25 } }
$in 包含 { name: { $in: ["张三", "李四"] } }
$nin 不包含 { name: { $nin: ["张三", "李四"] } }
$exists 字段存在 { email: { $exists: true } }
$regex 正则匹配 { name: { $regex: /^张/ } }
$mod 取模运算 % { price: { $mod: [10, 0] } }

复合查询

javascript 复制代码
// AND 查询(多个条件同时满足)
db.users.find({
  age: { $gte: 18, $lte: 30 },
  city: "北京"
})

// OR 查询
db.users.find({
  $or: [
    { age: { $lt: 18 } },
    { age: { $gt: 65 } }
  ]
})

// 查询 18岁,名字 张三和李四 的数据
db.users.find({
  age: 18,
  $or: [
    { name: '张三' },
    { name: '李四' }
  ]
})

数组查询

javascript 复制代码
// 查询数组包含特定元素
db.users.find({ hobbies: "游泳" })

// 查询数组包含多个元素
db.users.find({ hobbies: { $all: ["游泳", "读书"] } })

// 查询数组大小
db.users.find({ hobbies: { $size: 3 } })

// 查询数组元素位置
db.users.find({ "hobbies.0": "游泳" })

嵌套文档查询

javascript 复制代码
// 查询嵌套字段
db.users.find({ "address.city": "北京" })

// 查询嵌套文档
db.users.find({
  "address": {
    city: "北京",
    district: "朝阳区"
  }
})

投影查询

javascript 复制代码
// 只返回指定字段
db.users.find({}, { name: 1, age: 1 })

// 排除指定字段
db.users.find({}, { password: 0, _id: 0 })

// 嵌套字段投影
db.users.find({}, { "address.city": 1, name: 1 })

排序和分页

javascript 复制代码
// 排序
db.users.find().sort({ age: -1 })  // 降序
db.users.find().sort({ age: 1 })   // 升序

// 分页
db.users.find()
  .sort({ age: -1 })
  .skip(10)    // 跳过前10条
  .limit(5)    // 限制返回5条

// 获取总数
db.users.find().count()

// 当使用 limit 方法限制返回的记录数时,默认情况下 count 方法仍然返回全部记录条数
// 如果希望返回限制之后的记录数量,要使用 count(true) 或者 count(非0)
db.my_col.find().limit(1).count() // 1条
db.my_col.find().limit(1).count(true) // 全数

聚合管道

聚合管道是 MongoDB 最强大的功能之一,通过多个阶段处理数据。

常用阶段

阶段 作用 示例
$match 筛选文档 { $match: { age: { $gte: 18 } } }
$project 选择/重命名字段 { $project: { name: 1, age: 1 } }
$group 分组统计 { $group: { _id: "$city", count: { $sum: 1 } } }
$sort 排序 { $sort: { age: -1 } }
$limit 限制数量 { $limit: 10 }
$skip 跳过数量 { $skip: 5 }
$unwind 拆分数组 { $unwind: "$hobbies" }
$lookup 关联查询 { $lookup: { from: "orders", localField: "_id", foreignField: "userId", as: "orders" } }

聚合示例

基础统计
javascript 复制代码
// 按城市分组统计用户数量
db.users.aggregate([
  { $group: { _id: "$address.city", count: { $sum: 1 } } },
  { $sort: { count: -1 } }
])
复杂统计
javascript 复制代码
// 统计每个城市的用户年龄分布
db.users.aggregate([
  { $group: {
    _id: "$address.city",
    totalUsers: { $sum: 1 },
    avgAge: { $avg: "$age" },
    maxAge: { $max: "$age" },
    minAge: { $min: "$age" }
  }},
  { $sort: { totalUsers: -1 } }
])
关联查询
javascript 复制代码
// 用户和订单关联查询
db.users.aggregate([
  {
    $lookup: {
      from: "orders",
      localField: "_id",
      foreignField: "userId",
      as: "orders"
    }
  },
  {
    $project: {
      name: 1,
      orderCount: { $size: "$orders" },
      totalAmount: { $sum: "$orders.amount" }
    }
  }
])
数组处理
javascript 复制代码
// 统计每个爱好的用户数量
db.users.aggregate([
  { $unwind: "$hobbies" },
  { $group: { _id: "$hobbies", count: { $sum: 1 } } },
  { $sort: { count: -1 } }
])
复杂聚合示例
javascript 复制代码
db.col.aggregate([
    {$match: {"name": "xiaoming"}}, // 查找 xiaoming 同学的课程成绩
    {$group: {
    	_id: "$name", // 按 name 字段分组
    	total: {
    	  // $sum求和, $avg、$min、$max,分别表示求平均成绩、最低成绩、最高成绩
    		$sum: "$score"  // 计算每个name的score总和
    	}
    }},
    {$project: {
    	"_id":  0, // 不需要_id字段
    	"name": 1, // 需要name字段
    	"count": "$total" // 将 total 字段 重命名为 count
    }}, 
    {$sort: {"count":  -1, "name": 1}}, // 按分数降序排序;同样分数的,按课程名字升序排序
    {$skip: 1}, // 跳过一条数据
    {$limit: 1} // 只显示一条数据
])

索引优化

索引类型

单字段索引
javascript 复制代码
// 创建单字段索引
db.users.createIndex({ name: 1 })  // 升序
db.users.createIndex({ age: -1 })  // 降序
复合索引
javascript 复制代码
// 创建复合索引
db.users.createIndex({ name: 1, age: -1 })

// 查看查询计划
db.users.find({ name: "张三", age: { $gte: 18 } }).explain()
文本索引
javascript 复制代码
// 创建文本索引
db.articles.createIndex({ title: "text", content: "text" })

// 文本搜索
db.articles.find({ $text: { $search: "MongoDB 教程" } })
地理空间索引
javascript 复制代码
// 创建2dsphere索引
db.places.createIndex({ location: "2dsphere" })

// 地理空间查询
db.places.find({
  location: {
    $near: {
      $geometry: { type: "Point", coordinates: [116.3974, 39.9093] },
      $maxDistance: 1000
    }
  }
})

索引管理

javascript 复制代码
// 查看索引
db.users.getIndexes()

// 删除索引
db.users.dropIndex("name_1")

// 删除所有索引
db.users.dropIndexes()

// 重建索引
db.users.reIndex()

// 查看集合索引大小
db.col.totalIndexSize()

创建索引选项

javascript 复制代码
db.collection.createIndex(
    {key1: option1, key2: option2},    //key为要创建索引的字段,option为创建索引的方式:1 为升序,-1 为降序,可以对多个字段创建索引,称为复合索引
    {
        background: <boolean>,  //可选,建索引过程会阻塞其它数据库操作,background 设置为 true 可指定以后台方式创建索引,默认值为 false
        unique: <boolean>, //可选,建立的索引是否唯一。指定为true创建唯一索引。默认值为false
        name: <string>, //可选,索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称
        sparse: <boolean> //可选,对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档。默认值为 false
    }
)

索引优化建议

  1. 为经常查询的字段创建索引
  2. 复合索引字段顺序很重要:等值查询字段在前,范围查询字段在后
  3. 避免过多索引:索引会占用存储空间并影响写入性能
  4. 使用 explain() 分析查询性能

高级特性

事务处理

javascript 复制代码
// 开始事务
const session = db.getMongo().startSession()
session.startTransaction()

try {
  // 执行操作
  db.users.insertOne({ name: "张三", age: 25 }, { session })
  db.orders.insertOne({ userId: ObjectId(), amount: 100 }, { session })
  
  // 提交事务
  session.commitTransaction()
} catch (error) {
  // 回滚事务
  session.abortTransaction()
} finally {
  session.endSession()
}

副本集

MongoDB副本集是将数据同步在多个服务器的过程。

副本集提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性,并可以保证数据的安全性。

副本集还允许您从硬件故障和服务中断中恢复数据。

主节点会记录所有的操作oplog,从节点定期轮训oplog,对自己的副本执行这些操作,从而保证数据与主节点保持一致。

副本集由一个主节点和多个从节点组成。节点数量最多50个,推荐节点数是奇数个。

副本集的主节点挂掉后,会在集群内发起主节点的选举机制,从副节点中选举一个新的主节点出来。

分片集群

配置分片
javascript 复制代码
// 启用分片
sh.enableSharding("mydb")

// 设置分片键
sh.shardCollection("mydb.users", { userId: 1 })

// 查看分片状态
sh.status()

数据备份和恢复

bash 复制代码
# 备份数据库
mongodump --db mydb --out /backup/

# 恢复数据库
mongorestore --db mydb /backup/mydb/

# 压缩备份
mongodump --db mydb --gzip --archive=/backup/mydb.gz

# 备份指定集合
mongodump --db mydb --collection users --out /backup/

# 从压缩文件恢复
mongorestore --gzip --archive=/backup/mydb.gz

监控和维护

javascript 复制代码
// 查看数据库状态
db.stats()

// 查看集合状态
db.users.stats()

// 查看慢查询
db.setProfilingLevel(1, { slowms: 100 })
db.system.profile.find().sort({ ts: -1 }).limit(5)

// 查看当前操作
db.currentOp()

// 查看内存使用情况
db.serverStatus().mem

// 查看索引使用情况
db.users.aggregate([{ $indexStats: {} }])

// 重建索引
db.users.reIndex()

// 压缩集合
db.runCommand({ compact: "users" })

// 验证集合
db.users.validate()

安全配置

javascript 复制代码
// 创建用户
use admin
db.createUser({
  user: "admin",
  pwd: "password",
  roles: ["userAdminAnyDatabase", "dbAdminAnyDatabase", "readWriteAnyDatabase"]
})

// 创建普通用户
use mydb
db.createUser({
  user: "appuser",
  pwd: "apppassword",
  roles: ["readWrite"]
})

// 启用认证
// 启动时添加 --auth 参数
mongod --auth

性能优化

查询优化
javascript 复制代码
// 使用 explain() 分析查询性能
db.users.find({ name: "张三" }).explain("executionStats")

// 使用 hint() 强制使用特定索引
db.users.find({ name: "张三" }).hint({ name: 1 })
批量操作
javascript 复制代码
// 批量插入
db.users.insertMany([
  { name: "张三", age: 25 },
  { name: "李四", age: 30 },
  { name: "王五", age: 28 }
])

// 批量更新
db.users.bulkWrite([
  { updateOne: { filter: { name: "张三" }, update: { $set: { age: 26 } } } },
  { updateOne: { filter: { name: "李四" }, update: { $set: { age: 31 } } } }
])

NestJS 集成

安装和配置

bash 复制代码
npm install @nestjs/mongoose mongoose
npm install -D @types/mongoose

基本配置

typescript 复制代码
// app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost:27017/mydb'),
  ],
})
export class AppModule {}

环境变量配置

typescript 复制代码
// app.module.ts
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [
    ConfigModule.forRoot(),
    MongooseModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        uri: configService.get<string>('MONGODB_URI'),
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

Schema 定义

typescript 复制代码
// user.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';

export type UserDocument = User & Document;

@Schema({ 
  timestamps: true,
  collection: 'users',
  versionKey: false
})
export class User {
  @Prop({ type: Types.ObjectId, auto: true })
  _id: Types.ObjectId;

  @Prop({ required: true, trim: true, maxlength: 50 })
  name: string;

  @Prop({ 
    required: true, 
    unique: true, 
    lowercase: true,
    match: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  })
  email: string;

  @Prop({ min: 0, max: 150 })
  age: number;

  @Prop({ 
    type: [String], 
    default: [],
    validate: {
      validator: function(v: string[]) {
        return v.length <= 10;
      },
      message: 'Hobbies cannot exceed 10 items'
    }
  })
  hobbies: string[];

  @Prop({
    type: {
      city: { type: String, required: true },
      district: String,
      coordinates: {
        type: [Number],
        index: '2dsphere'
      }
    }
  })
  address: {
    city: string;
    district?: string;
    coordinates?: [number, number];
  };

  @Prop({ 
    type: Date, 
    default: Date.now,
    expires: 86400 // 24小时后过期
  })
  lastLogin: Date;

  @Prop({ default: false })
  isDeleted: boolean;

  @Prop()
  deletedAt: Date;
}

export const UserSchema = SchemaFactory.createForClass(User);

// 添加索引
UserSchema.index({ email: 1 });
UserSchema.index({ 'address.city': 1, age: -1 });
UserSchema.index({ lastLogin: 1 }, { expireAfterSeconds: 0 });

// 添加虚拟字段
UserSchema.virtual('fullAddress').get(function() {
  return `${this.address.city} ${this.address.district || ''}`.trim();
});

// 添加方法
UserSchema.methods.isAdult = function() {
  return this.age >= 18;
};

// 添加静态方法
UserSchema.statics.findByCity = function(city: string) {
  return this.find({ 'address.city': city });
};

Service 实现

typescript 复制代码
// user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel, InjectConnection } from '@nestjs/mongoose';
import { Model, Connection } from 'mongoose';
import { User, UserDocument } from './user.schema';

@Injectable()
export class UserService {
  constructor(
    @InjectModel(User.name) private userModel: Model<UserDocument>,
    @InjectConnection() private connection: Connection,
  ) {}

  async create(createUserDto: any): Promise<User> {
    try {
      const user = new this.userModel(createUserDto);
      return await user.save();
    } catch (error) {
      if (error.code === 11000) {
        throw new ConflictException('Email already exists');
      }
      throw new InternalServerErrorException('Failed to create user');
    }
  }

  async findAll(): Promise<User[]> {
    return this.userModel.find().exec();
  }

  async findOne(id: string): Promise<User> {
    return this.userModel.findById(id).exec();
  }

  async update(id: string, updateUserDto: any): Promise<User> {
    return this.userModel.findByIdAndUpdate(id, updateUserDto, { new: true }).exec();
  }

  async remove(id: string): Promise<User> {
    return this.userModel.findByIdAndDelete(id).exec();
  }

  // 分页查询
  async findWithPagination(page: number = 1, limit: number = 10, filters: any = {}) {
    const skip = (page - 1) * limit;
    
    const [users, total] = await Promise.all([
      this.userModel.find(filters).skip(skip).limit(limit).exec(),
      this.userModel.countDocuments(filters).exec()
    ]);

    return {
      data: users,
      pagination: {
        page,
        limit,
        total,
        pages: Math.ceil(total / limit)
      }
    };
  }

  // 复杂查询示例
  async findByAgeRange(minAge: number, maxAge: number): Promise<User[]> {
    return this.userModel.find({
      age: { $gte: minAge, $lte: maxAge }
    }).exec();
  }

  // 聚合查询示例
  async getUsersByCity(): Promise<any[]> {
    return this.userModel.aggregate([
      { $group: { _id: '$address.city', count: { $sum: 1 } } },
      { $sort: { count: -1 } }
    ]).exec();
  }

  // 软删除
  async softDelete(id: string): Promise<User> {
    return this.userModel.findByIdAndUpdate(
      id,
      { isDeleted: true, deletedAt: new Date() },
      { new: true }
    ).exec();
  }

  async findActive(): Promise<User[]> {
    return this.userModel.find({ isDeleted: false }).exec();
  }

  // 事务处理
  async createUserWithProfile(userData: any, profileData: any): Promise<User> {
    const session = await this.connection.startSession();
    
    try {
      session.startTransaction();
      
      const user = new this.userModel(userData);
      await user.save({ session });
      
      // 创建关联的profile
      const profile = new this.profileModel({ ...profileData, userId: user._id });
      await profile.save({ session });
      
      await session.commitTransaction();
      return user;
    } catch (error) {
      await session.abortTransaction();
      throw error;
    } finally {
      session.endSession();
    }
  }

  // 性能优化方法
  async findAllLean(): Promise<any[]> {
    return this.userModel.find().lean().exec();
  }

  async findNamesOnly(): Promise<any[]> {
    return this.userModel.find().select('name email').exec();
  }
}

Controller 实现

typescript 复制代码
// user.controller.ts
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  create(@Body() createUserDto: any) {
    return this.userService.create(createUserDto);
  }

  @Get()
  findAll(@Query('page') page?: string, @Query('limit') limit?: string, @Query('minAge') minAge?: string, @Query('maxAge') maxAge?: string) {
    if (minAge && maxAge) {
      return this.userService.findByAgeRange(+minAge, +maxAge);
    }
    if (page || limit) {
      return this.userService.findWithPagination(+page || 1, +limit || 10);
    }
    return this.userService.findAll();
  }

  @Get('stats')
  getStats() {
    return this.userService.getUsersByCity();
  }

  @Get('active')
  findActive() {
    return this.userService.findActive();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.userService.findOne(id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateUserDto: any) {
    return this.userService.update(id, updateUserDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.userService.remove(id);
  }

  @Delete(':id/soft')
  softRemove(@Param('id') id: string) {
    return this.userService.softDelete(id);
  }
}

数据验证

typescript 复制代码
// create-user.dto.ts
import { IsEmail, IsString, IsNumber, IsOptional, IsArray, Min, Max, ArrayMaxSize } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @IsOptional()
  name?: string;

  @IsEmail()
  email: string;

  @IsNumber()
  @Min(0)
  @Max(150)
  @IsOptional()
  age?: number;

  @IsArray()
  @ArrayMaxSize(10)
  @IsOptional()
  hobbies?: string[];

  @IsOptional()
  address?: {
    city: string;
    district?: string;
  };
}

连接池配置

typescript 复制代码
// app.module.ts
MongooseModule.forRootAsync({
  useFactory: () => ({
    uri: 'mongodb://localhost:27017/mydb',
    useNewUrlParser: true,
    useUnifiedTopology: true,
    maxPoolSize: 10, // 最大连接数
    minPoolSize: 5, // 最小连接数
    maxIdleTimeMS: 30000, // 最大空闲时间
    serverSelectionTimeoutMS: 5000, // 服务器选择超时
    socketTimeoutMS: 45000, // Socket超时
    bufferMaxEntries: 0, // 禁用mongoose缓冲
    bufferCommands: false, // 禁用mongoose缓冲
  }),
}),

数据迁移

typescript 复制代码
// migration.service.ts
@Injectable()
export class MigrationService {
  constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}

  async migrateUserData() {
    const users = await this.userModel.find({ address: { $exists: false } });
    
    for (const user of users) {
      await this.userModel.updateOne(
        { _id: user._id },
        { $set: { address: { city: 'Unknown' } } }
      );
    }
  }
}

最佳实践

1. 数据建模

  • 嵌入 vs 引用:小文档且不经常变化的数据适合嵌入,大文档或经常变化的数据适合引用
  • 避免深层嵌套:嵌套层级不要超过3层
  • 合理使用数组:数组元素不要超过100个

2. 查询优化

typescript 复制代码
// 使用 lean() 提高查询性能
async findAllLean(): Promise<any[]> {
  return this.userModel.find().lean().exec();
}

// 使用 select() 只选择需要的字段
async findNamesOnly(): Promise<any[]> {
  return this.userModel.find().select('name email').exec();
}

// 使用 explain() 分析查询性能
db.users.find({ name: "张三" }).explain("executionStats")

3. 错误处理

typescript 复制代码
async create(createUserDto: CreateUserDto): Promise<User> {
  try {
    const user = new this.userModel(createUserDto);
    return await user.save();
  } catch (error) {
    if (error.code === 11000) {
      throw new ConflictException('Email already exists');
    }
    throw new InternalServerErrorException('Failed to create user');
  }
}

4. 性能监控

javascript 复制代码
// 查看慢查询
db.setProfilingLevel(1, { slowms: 100 })

// 查看索引使用情况
db.users.aggregate([{ $indexStats: {} }])

// 查看内存使用情况
db.serverStatus().mem

5. 安全建议

  • 启用认证和授权
  • 使用 SSL/TLS 加密连接
  • 定期备份数据
  • 监控数据库访问日志
  • 使用强密码和定期更换

6. 常见问题

  • 连接超时:检查网络连接和防火墙设置
  • 查询慢:分析查询计划,添加合适的索引
  • 内存不足:调整缓存大小,优化查询
  • 数据不一致:使用事务保证数据一致性

7. 连接管理

typescript 复制代码
// 使用连接池
const connectionOptions = {
  maxPoolSize: 10,
  minPoolSize: 5,
  maxIdleTimeMS: 30000,
  serverSelectionTimeoutMS: 5000,
  socketTimeoutMS: 45000,
};

总结

MongoDB 是一个功能强大的 NoSQL 数据库,特别适合处理非结构化数据和需要快速开发的应用。通过合理使用索引、聚合管道和最佳实践,可以构建高性能的应用程序。

与 NestJS 的集成提供了类型安全和良好的开发体验,是现代 Node.js 应用开发的优秀选择。

关键要点

  1. 灵活的数据模型:支持复杂的数据结构和嵌套文档
  2. 强大的查询能力:丰富的查询操作符和聚合管道
  3. 高性能:通过索引和优化策略实现高性能查询
  4. 可扩展性:支持副本集和分片集群
  5. 开发友好:与 NestJS 等现代框架完美集成
相关推荐
前端小书生5 小时前
NestJs
前端·nestjs
濮水大叔6 小时前
Vona ORM分表全攻略
typescript·node.js·nestjs
cookqq6 小时前
MongoDB源码delete分析观察者getOpObserver()->onDelete
数据库·sql·mongodb·nosql
荷兰小香猪_017 小时前
MongoDB数据类型与python操作
数据库·mongodb
木木子999910 小时前
MongoDB集合学习笔记
笔记·学习·mongodb
木木子999910 小时前
MongoDB文档规范
数据库·mongodb
Python大数据分析10 小时前
mongoDB本地windows安装和初始化
mongodb
开着拖拉机回家21 小时前
【MongoDB】mongoDB数据迁移
数据库·mongodb·nosql·数据库迁移·mongodump·mongorestore
木木子999921 小时前
MongoDB Integer
数据库·mongodb