新闻中心

Mongoose _id字段自定义为Number类型:实现与验证

2025-11-12
浏览次数:
返回列表

Mongoose _id字段自定义为Number类型:实现与验证

本教程详细指导如何在mongoose中将`_id`字段自定义为`number`类型,并实现严格的正整数验证。通过创建自定义schematype,确保`_id`的数据完整性。同时,文章将深入探讨mongodb/mongoose环境下`_id`字段自增长的实现策略,指出仅定义类型无法自动生成序列号,需要额外的业务逻辑支持,例如利用独立的计数器集合。

引言

Mongoose默认使用ObjectId作为文档的_id字段类型,它是一个12字节的唯一标识符。然而,在某些业务场景下,开发者可能希望将_id设置为Number类型,并且期望它能像关系型数据库的主键一样自动递增。直接在Schema中定义_id: Number并不能实现自动递增的功能,它只会将_id字段的类型限制为数字,但仍会由MongoDB生成ObjectId,或者要求用户手动提供一个数字。为了实现_id为数字类型并进行有效验证,我们需要创建自定义的Mongoose SchemaType。

自定义 NumberId SchemaType

Mongoose允许开发者定义自己的SchemaType,以扩展其内置类型的功能。这里,我们将创建一个名为NumberId的自定义类型,它将强制_id字段为正整数。

  1. 继承 mongoose.SchemaType: 自定义SchemaType需要继承自mongoose.SchemaType。
  2. 实现 cast() 方法: cast() 方法是自定义类型中的核心,它负责将输入值转换为期望的类型,并在转换失败或值不符合要求时抛出错误。在这个方法中,我们将验证输入值是否为数字、整数且为非负数。
  3. 注册新的SchemaType: 将自定义类型注册到mongoose.Schema.Types中,使其可以在Schema定义中被引用。

以下是实现NumberId自定义SchemaType的代码:

const mongoose = require('mongoose');

// 1. 定义自定义SchemaType:NumberId
function NumberId(key, options) {
  // 调用父类构造函数,并指定类型名称为 'NumberId'
  mongoose.SchemaType.call(this, key, options, 'NumberId');
}

// 2. 继承mongoose.SchemaType的原型
NumberId.prototype = Object.create(mongoose.SchemaType.prototype);

// 3. 实现cast()方法进行类型转换和验证
NumberId.prototype.cast = function (val) {
  if (typeof val !== 'number') {
    throw new Error('NumberId: ' + val + ' is not a number');
  }
  if (val % 1 !== 0) { // 检查是否为整数
    throw new Error('NumberId: ' + val + ' is not an integer');
  }
  if (val < 0) { // 检查是否为非负数
    throw new Error('NumberId: ' + val + ' is negative');
  }
  return val;
};

// 4. 注册新的SchemaType
mongoose.Schema.Types.NumberId = NumberId;

上述代码定义了一个名为NumberId的SchemaType,它强制要求字段值必须是正整数。任何不符合这些条件的值都将在保存时抛出错误。

在Schema中应用自定义 NumberId 类型

一旦自定义类型被注册,就可以像使用Mongoose内置类型一样,在Schema定义中引用它。下面是创建一个模型并使用NumberId类型作为_id字段的示例:

const mongoose = require('mongoose');

// ... (此处省略NumberId SchemaType的定义和注册代码,假设已完成) ...
// 注册新的SchemaType
// mongoose.Schema.Types.NumberId = NumberId; // 确保此行已执行

// 创建一个使用我们自定义NumberId类型的Schema
const mySchema = new mongoose.Schema(
  {
    _id: { type: mongoose.Schema.Types.NumberId, required: true, unique: true },
    name: String, // 示例:其他字段
  },
  {
    autoIndex: false // 生产环境建议关闭自动创建索引,以手动管理索引
  }
);

// 创建模型
const MyModel = mongoose.model('MyModel', mySchema);

// 连接MongoDB并进行示例操作
const mongoUri = 'mongodb://localhost:27017/test_db';

async function run() {
  await mongoose.connect(mongoUri, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  });
  console.log('MongoDB connected.');

  try {
    // 尝试创建文档,_id需要手动提供
    await MyModel.create({ _id: 1, name: 'Item One' });
    await MyModel.create({ _id: 2, name: 'Item Two' });
    await MyModel.create({ _id: 3, name: 'Item Three' });
    console.log('Documents created successfully with custom Number _id.');

    // 尝试创建无效_id的文档,这些操作将抛出错误
    // await MyModel.create({ _id: -1, name: 'Invalid Item' });
    // await MyModel.create({ _id: 1.5, name: 'Invalid Item' });
    // await MyModel.create({ _id: 'abc', name: 'Invalid Item' });

  } catch (error) {
    console.error('Error creating documents:', error.message);
  } finally {
    await mongoose.disconnect();
    console.log('MongoDB disconnected.');
  }
}

run();

在上述示例中,_id字段被明确指定为mongoose.Schema.Types.NumberId类型,并且required: true和unique: true确保了其唯一性和存在性。需要注意的是,尽管我们定义了NumberId类型,但在创建文档时,_id的值仍然需要手动提供。

Tanka Tanka

具备AI长期记忆的下一代团队协作沟通工具

Tanka 146 查看详情 Tanka

关于 _id 自增长的实现策略

虽然我们已经成功地将_id定义为Number类型并实现了验证,但MongoDB和Mongoose本身并没有提供像关系型数据库那样内置的自动递增Number类型_id机制。如果需要实现_id的自增长,通常需要额外的逻辑。以下是两种常见的实现策略:

  1. 使用独立的计数器集合 (Counters Collection): 这是MongoDB中实现自增长序列最常见且推荐的方法。

    • 创建一个专门的集合(例如counters),其中每个文档存储一个序列名称和对应的当前值。
    • 在每次需要生成新_id时,原子性地查找并更新(findOneAndUpdate)该计数器文档,获取递增后的值。
    • 将获取到的值赋给新文档的_id。

    示例伪代码(结合Mongoose的pre('s*e')钩子):

    // 假设MyModel的Schema定义如上
    mySchema.pre('s*e', async function(next) {
      // 仅在新文档且_id未手动指定时执行自增长逻辑
      if (this.isNew && !this._id) {
        try {
          // 原子性地查找并更新计数器
          const counter = await mongoose.connection.db.collection('counters').findOneAndUpdate(
            { _id: 'myModelId' }, // 使用模型名称作为计数器ID
            { $inc: { sequence_value: 1 } }, // 递增sequence_value
            { returnDocument: 'after', upsert: true } // 返回更新后的文档,如果不存在则创建
          );
          this._id = counter.value.sequence_value; // 将递增后的值赋给_id
          next();
        } catch (error) {
          next(error); // 捕获并传递错误
        }
      } else {
        next(); // 如果是更新操作或_id已指定,则跳过
      }
    });

    这种方法确保了在并发环境下的序列号唯一性和正确性。

  2. 利用外部服务或库: 对于更复杂的分布式系统,可以考虑使用专门的ID生成服务(如UUID、Snowflake算法)或第三方库来生成唯一的数字ID。但请注意,这些方法生成的ID通常不是连续的序列号。

重要提示: 上述自定义NumberId SchemaType主要用于_id的类型验证,确保其为有效的正整数。它本身不提供自增长功能。实现自增长需要结合Mongoose的pre('s*e')钩子和MongoDB的原子操作来管理序列。

总结与注意事项

本教程展示了如何通过自定义Mongoose SchemaType将_id字段设置为Number类型并

以上就是Mongoose _id字段自定义为Number类型:实现与验证的详细内容,更多请关注其它相关文章!


# 自己的  # 济南网站建设美丽  # 山东产品网站推广业务  # 山东家政网站建设推广价格  # 达内网站建设中心  # 纸巾营销推广设计方案  # seo 判刑  # 医院宣传科网站建设  # 怎么去经营seo  # 海珠网站关键词排名  # 推广视频网站建设运营  # 后端  # 的是  # go  # 设置为  # 不符合  # 正整数  # 抛出  # 创建一个  # 文档  # 自定义  # red  # ai  # 字节  # mongodb 


相关栏目: 【 科技资讯46185 】 【 网络学院92790


相关推荐: css元素hover动画延迟生效怎么办_使用animation-delay调整触发时间  Sublime Text怎么显示空格和制表符_Sublime显示不可见字符设置  提升Kafka消费者健壮性:会话超时处理与消息处理语义  在J*a中如何捕获IndexOutOfBoundsException_索引越界异常防护方法说明  在Runstone环境中高效处理TasteDive API的JSON数据  Sublime Text怎么设置垂直标尺_Sublime配置Rulers规范代码长度  4399免费游戏网址入口 4399小游戏免费入口点开即玩  在WordPress中通过REST API获取BasicAuth保护的远程文章  Python类型检查:优化关联可选属性的Mypy推断策略  Golang如何测试channel通信行为_Golang channel通信测试与分析方法  vivo浏览器怎么扫描二维码 vivo浏览器内置扫一扫功能使用方法  如何在低配置电脑上搭建轻量级J*a环境_占用更小的环境选择技巧  LINUX怎么设置定时任务_LINUX crontab配置教程  J*aScript对象创建方式_J*aScript设计模式应用  神经网络二分类模型训练异常:高损失与完美验证准确率的排查与修正  AO3最新可访问网址 Archive of Our Own官方在线入口  Win11怎么合并任务栏图标 Win11开启任务栏合并减少图标占空间【方法】  飞书妙记怎样用语音转文字速记_飞书妙记用语音转文字速记【速记方法】  MAC如何安全彻底地删除文件_MAC使用终端命令确保文件无法被恢复  vivo手机参数配置怎么增强信号_vivo手机参数配置信号增强方法  如何使用Node.js csv 包按条件移除含空字段的CSV记录  照顾宝贝2小游戏免费秒玩入口  抓大鹅解压小游戏 抓大鹅摸鱼解压入口  腾讯视频怎么使用多账号家庭管理_腾讯视频家庭多账号统一管理与权限分配教程  J*aScript打印功能_j*ascript输出控制  新三国志曹操传110级星符试炼夏侯渊极难攻略  Typer应用中动态命令行参数的解析与处理  Mac终端命令大全_Mac常用Terminal指令速查  Django表单验证失败时保留用户输入数据的最佳实践  Go语言中对Map值调用带指针接收者方法:原理与最佳实践  不会效仿卡普空!《铁拳》制作人澄清:不采取赛事付费|直播|  最新韩小圈网页版登录入口_官网在线观看官方链接  J*aScript中localStorage数据的获取、清洗与格式化教程  魅族17怎样用浏览器译外语网页_iPhone魅族17浏览器译外语网页【即时翻译】  c++如何使用std::memory_order控制原子操作顺序_c++ C++11内存模型详解  HuggingFaceEmbeddings中向量嵌入维度调整的限制与理解  微博网页版主页入口 微博官方网站免登录访问  MongoDB聚合管道:正确匹配对象数组中_id的方法  J*a TimerTask中HashMap意外清空的深层原因与解决方案  “在文档元素之后找到了标记”是什么错误? 检查并修复XML中多个根元素的3个方法  Node.js CSV 数据处理:基于字段空值条件过滤整条记录的策略  谷歌google账号怎么注册账号 谷歌账号注册官方流程  2025AO3夸克浏览器通道_AO3手机HTTPS安全入口分享  俄罗斯Yandex搜索引擎入口_Yandex官网免登录一键访问  Lar*el用户头像管理:实现图片缩放、存储与旧文件安全删除的最佳实践  C++ map遍历方法大全_C++ map迭代器使用总结  PDF文件体积过大处理_PDF压缩技巧详解  Win11文件资源管理器卡顿怎么修 Win11重置资源管理器进程优化响应速度【修复方法】  QQ邮箱网页版登录入口 QQ邮箱官方在线使用平台  Windows10怎么开启存储感知 Windows10系统设置自动清理临时文件释放C盘空间【教程】 

搜索