Mongoose - MongoDB对象建模工具

1. 什么是Mongoose?

Mongoose是一个优雅的MongoDB对象建模工具,用于在Node.js环境中工作。它提供了一个直接的、基于模式的解决方案,用于对应用程序数据进行建模,包括内置的数据验证、查询构建、中间件支持和业务逻辑钩子。

1.1 核心特性

  • 模式定义:通过Schema定义数据结构和验证规则
  • 中间件支持:支持文档生命周期的钩子函数
  • 查询构建器:提供链式调用的查询API
  • 数据验证:内置验证规则,确保数据完整性
  • 虚拟属性:支持计算属性
  • 聚合管道:支持MongoDB的聚合操作

2. 快速开始

2.1 安装

npm install mongoose

2.2 基本用法

const mongoose = require('mongoose');

// 连接数据库
mongoose.connect('mongodb://localhost:27017/test', {
  useNewUrlParser: true,
  useUnifiedTopology: true
});

// 定义模式
const userSchema = new mongoose.Schema({
  name: String,
  email: {
    type: String,
    required: true,
    unique: true
  },
  age: Number,
  createdAt: {
    type: Date,
    default: Date.now
  }
});

// 创建模型
const User = mongoose.model('User', userSchema);

// 创建文档
const newUser = new User({
  name: '张三',
  email: 'zhangsan@example.com',
  age: 30
});

// 保存文档
newUser.save()
  .then(user => console.log('用户创建成功:', user))
  .catch(err => console.error('创建用户时出错:', err));

3. 模式定义

3.1 基本类型

Mongoose支持以下基本数据类型:

  • String
  • Number
  • Date
  • Buffer
  • Boolean
  • Mixed
  • ObjectId
  • Array

3.2 模式选项

const schema = new mongoose.Schema({
  // 字段定义
  field1: String,
  field2: {
    type: Number,
    required: true,
    min: 0,
    max: 100
  },
  field3: {
    type: Date,
    default: Date.now
  }
}, {
  // 模式选项
  timestamps: true, // 自动添加createdAt和updatedAt字段
  collection: 'custom_collection_name' // 自定义集合名
});

4. 查询操作

4.1 基本查询

// 查找所有文档
User.find({})
  .then(users => console.log('所有用户:', users));

// 按条件查找
User.find({ age: { $gt: 25 } })
  .then(users => console.log('年龄大于25的用户:', users));

// 查找单个文档
User.findOne({ email: 'zhangsan@example.com' })
  .then(user => console.log('找到的用户:', user));

// 按ID查找
User.findById('60d7f8e4b4e4a0001c8e4b4a')
  .then(user => console.log('按ID找到的用户:', user));

4.2 高级查询

// 排序
User.find({})
  .sort({ age: -1 }) // 降序
  .then(users => console.log('按年龄排序的用户:', users));

// 分页
User.find({})
  .skip(10) // 跳过前10个
  .limit(5) // 限制返回5个
  .then(users => console.log('分页结果:', users));

// 字段选择
User.find({})
  .select('name email') // 只返回name和email字段
  .then(users => console.log('只包含指定字段的用户:', users));

5. 更新操作

5.1 基本更新

// 更新单个文档
User.updateOne({ email: 'zhangsan@example.com' }, {
  $set: { age: 31 }
})
  .then(result => console.log('更新结果:', result));

// 更新多个文档
User.updateMany({ age: { $lt: 18 } }, {
  $set: { status: 'minor' }
})
  .then(result => console.log('更新结果:', result));

// 按ID更新
User.findByIdAndUpdate('60d7f8e4b4e4a0001c8e4b4a', {
  $set: { name: '李四' }
}, { new: true }) // 返回更新后的文档
  .then(user => console.log('更新后的用户:', user));

6. 删除操作

// 删除单个文档
User.deleteOne({ email: 'zhangsan@example.com' })
  .then(result => console.log('删除结果:', result));

// 删除多个文档
User.deleteMany({ age: { $gt: 60 } })
  .then(result => console.log('删除结果:', result));

// 按ID删除
User.findByIdAndDelete('60d7f8e4b4e4a0001c8e4b4a')
  .then(user => console.log('删除的用户:', user));

7. 中间件

7.1 文档中间件

// 保存前的钩子
userSchema.pre('save', function(next) {
  // 在保存前执行操作
  this.updatedAt = new Date();
  next();
});

// 保存后的钩子
userSchema.post('save', function(doc, next) {
  // 在保存后执行操作
  console.log('文档已保存:', doc._id);
  next();
});

7.2 查询中间件

// 查询前的钩子
userSchema.pre('find', function(next) {
  // 在查询前执行操作
  this.where({ active: true });
  next();
});

8. 数据验证

8.1 内置验证器

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, '姓名是必填的'],
    minlength: [2, '姓名至少需要2个字符'],
    maxlength: [50, '姓名最多50个字符']
  },
  email: {
    type: String,
    required: true,
    unique: true,
    validate: {
      validator: function(v) {
        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
      },
      message: props => `${props.value} 不是有效的邮箱地址!`
    }
  },
  age: {
    type: Number,
    min: [0, '年龄不能为负数'],
    max: [120, '年龄不能超过120岁']
  }
});

9. 关联关系

9.1 引用关系

const postSchema = new mongoose.Schema({
  title: String,
  content: String,
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  }
});

const Post = mongoose.model('Post', postSchema);

// 填充关联数据
Post.find({})
  .populate('author') // 填充author字段
  .then(posts => console.log('带作者信息的帖子:', posts));

10. 实际应用示例

10.1 用户认证系统

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    unique: true
  },
  email: {
    type: String,
    required: true,
    unique: true
  },
  password: {
    type: String,
    required: true
  },
  role: {
    type: String,
    enum: ['user', 'admin'],
    default: 'user'
  }
});

// 密码加密中间件
userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
  next();
});

// 验证密码方法
userSchema.methods.validatePassword = async function(password) {
  return await bcrypt.compare(password, this.password);
};

const User = mongoose.model('User', userSchema);

10.2 博客系统

const commentSchema = new mongoose.Schema({
  content: {
    type: String,
    required: true
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  },
  post: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Post'
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

const postSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },
  content: {
    type: String,
    required: true
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  },
  tags: [String],
  comments: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Comment'
  }],
  createdAt: {
    type: Date,
    default: Date.now
  },
  updatedAt: {
    type: Date,
    default: Date.now
  }
});

// 更新时间中间件
postSchema.pre('save', function(next) {
  this.updatedAt = new Date();
  next();
});

const User = mongoose.model('User', userSchema);
const Post = mongoose.model('Post', postSchema);
const Comment = mongoose.model('Comment', commentSchema);

11. 性能优化

11.1 索引

// 在模式中定义索引
const userSchema = new mongoose.Schema({
  email: {
    type: String,
    unique: true, // 创建唯一索引
    index: true // 创建普通索引
  },
  age: {
    type: Number,
    index: true // 创建普通索引
  }
});

// 创建复合索引
userSchema.index({ email: 1, age: -1 });

11.2 批量操作

// 批量插入
User.insertMany([
  { name: '张三', email: 'zhangsan@example.com' },
  { name: '李四', email: 'lisi@example.com' },
  { name: '王五', email: 'wangwu@example.com' }
])
  .then(users => console.log('批量插入成功:', users.length))
  .catch(err => console.error('批量插入失败:', err));

12. 最佳实践

12.1 连接管理

// 连接数据库
const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGO_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      useCreateIndex: true,
      useFindAndModify: false
    });
    console.log('MongoDB连接成功');
  } catch (error) {
    console.error('MongoDB连接失败:', error.message);
    process.exit(1);
  }
};

connectDB();

// 监听连接事件
mongoose.connection.on('error', err => {
  console.error('MongoDB连接错误:', err);
});

mongoose.connection.on('disconnected', () => {
  console.log('MongoDB连接断开');
});

12.2 错误处理

// 全局错误处理中间件
app.use((err, req, res, next) => {
  if (err.name === 'ValidationError') {
    // 验证错误
    const errors = Object.values(err.errors).map(val => val.message);
    return res.status(400).json({ errors });
  }
  
  if (err.code === 11000) {
    // 唯一索引错误
    return res.status(400).json({ error: '该值已存在' });
  }
  
  // 其他错误
  console.error(err);
  res.status(500).json({ error: '服务器内部错误' });
});

13. 总结

Mongoose是一个功能强大的MongoDB对象建模工具,它大大简化了Node.js应用程序与MongoDB的交互。通过模式定义、数据验证、中间件支持和丰富的查询API,Mongoose使得MongoDB的使用更加直观和高效。

13.1 核心优势

  • 模式化数据:通过Schema定义数据结构,提高代码可维护性
  • 内置验证:确保数据完整性和一致性
  • 丰富的查询API:支持复杂的查询操作
  • 中间件系统:灵活的钩子函数,用于处理文档生命周期事件
  • 关联关系:支持文档之间的引用和填充

13.2 适用场景

  • Web应用后端:处理用户数据、内容管理等
  • API服务:构建RESTful或GraphQL API
  • 实时应用:与Socket.io等结合使用
  • 数据处理:批量数据处理和分析

通过本教程的学习,您应该已经掌握了Mongoose的基本用法和高级特性,可以开始在实际项目中应用它来构建强大的MongoDB驱动的应用程序了。

« 上一篇 Prisma 教程 下一篇 » 60. Sequelize - Node.js ORM框架