mongoose令人迷惑的_id 和id

在 Mongoose 中,_idid 是与文档标识符(identifier)相关的两个字段,但它们有一些关键区别和特定的用途:

  1. _id 字段:
    • MongoDB 内部字段_id 是 MongoDB 内部默认的主键字段,每个文档都有一个唯一的 _id 字段,默认类型为 ObjectId。
    • 默认生成:如果在插入文档时没有指定 _id,MongoDB 会自动生成一个唯一的 ObjectId 作为 _id
    • 数据库层次_id 是实际存储在数据库中的字段,并且在查询、索引等操作中使用。
  2. id 字段:
    • 虚拟字段id 是 Mongoose 提供的虚拟字段,它是将 _id 字段的值转换为字符串类型。默认情况下,Mongoose 会为每个文档创建一个 id 虚拟字段,等同于 this._id.toString()
    • JavaScript 层次id 字段不是实际存储在数据库中的字段,而是在应用层面上通过 Mongoose 的模式(schema)提供的便利字段,便于使用。
    • 简化操作:使用 id 字段可以避免直接处理 ObjectId 类型,特别是在需要将文档标识符传递到前端或进行 JSON 序列化时非常方便。

说白了,id 是_id 的一个计算属性,为了简化操作。

刚开始会觉得这个功能很便捷,并且mongoose做了很多自动转换,很多地方不需要区分他们俩,觉得很便捷。

但问题也随之而来,首先是代码中有的地方用id,有的用_id, 看起来很混乱;其次对于前端来讲,返回的_id 和id 都是字符串,没有区别,相似又冗余;再然后是,在服务端,对于一些嵌套doc , 就只有_id , 没有id。

对于findOne({_id}),严格上讲,要传入ObjectId 才能查询,但mongoose 做了自动转换,一般我们传入字符串就行。但在 Mongoose 的 aggregate 操作中,字符串形式的 id 不会自动转换为 ObjectId。经常导致一些隐藏的问题,并且这个地方传错了也不会报错,只是查询结果不对。例如我今天遇到的一个bug.

Example.aggregate([
  {
    $match: {
      _id: {$nin:[ObjectId('xxx'),'zzz']}
    }
  },
  {
    $project: {
      name: 1
    }
  }
])

后面的’zzz’ 因为是字符串id, 实际上不生效,但也不报错,最终结果不对,十分隐蔽的bug.

所以最后,虽然mongoose 做了很多兼容来简化_id 的操作,一开始大家觉得不用区分了,但踩到一些坑之后才明白,最好严格区分id 的类型和不同常见的用法,不要含糊使用。

Leave a Comment

邮箱地址不会被公开。 必填项已用*标注