域名 AXUM.RS 将于 2025 年 10 月到期。我们无意再对其进行续费,如果你有意接续这个域名,请与我们取得联系。
  • AXUM.RS 现仅需人民币 3000 元(大写:叁仟元整。接受适度议价
  • 按照行业规则,AXUM.RS 到期后,大概率会进入长时间的赎回期,该期间内,如果你想拥有该域名,将要付出高额的费用
  • 我们已启用 AXUM.EU.ORG 域名,并将持续运营
  • 仅接受微信或支付宝交易
如果你对 AXUM.RS 有兴趣,请和我们进行联系:

MongoDB 插入数据

本章我们将讨论 MongoDB 插入数据的方法。

模型定义

使用自定义ID

MongoDB 使用 _id 字段作为主键来标识不同的文档(document,对应关系型数据库中的“记录”):

  • 如果未显式指定 _id 字段的值,MongDB 会自动分配全局唯一的 ObjectId

    #[derive(Debug, Serialize, Deserialize)]
    pub struct Note {
        #[serde(rename = "_id")]
        pub id: mongodb::bson::oid::ObjectId,
        pub title: String,
        pub content: String,
    }
    
  • 我们可以自定义 _id 字段的值,比如使用 xid

     #[derive(Debug, Serialize, Deserialize)]
     pub struct Note {
         #[serde(rename = "_id")]
         pub id: String,
         pub title: String,
         pub content: String,
     }
    
  • 尽量不要使用关系型数据库中整数型自动编号。虽然 MongoDB 只认 _id 字段,而不区分它的数据类型,但由于 MongoDB 天生为集群设计的,所以为了避免扩展时出现主键冲突,不建议使用自动编号作为主键值。

日期时间的处理

我们需要安装带有 chrono-0_4 feature 的 bson,利用它提供 bson::serde_helpers::chrono_datetime_as_bson_datetime 转换函数将 chrono 的日期时间值转换为 MongDB bson 所需要的格式:

#[derive(Debug, Serialize, Deserialize)]
pub struct Note {
    #[serde(rename = "_id")]
    pub id: String,
    pub title: String,
    pub content: String,
    #[serde(with = "bson::serde_helpers::chrono_datetime_as_bson_datetime")]
    pub created_at: chrono::DateTime<chrono::Utc>,
}

如果你使用的是标准库的日期时间,则可以使用 mongodb crate 带的转换函数

模型方法

为了方便操作,我们给这个模型定义 new() 方法:

impl Note {
    pub fn new(title: impl Into<String>, content: impl Into<String>) -> Self {
        Self {
            id: xid::new().to_string(),
            title: title.into(),
            content: content.into(),
            created_at: chrono::Utc::now(),
        }
    }
}

数据库连接

  • 使用 Client 连接到 MongoDB 服务器
  • 然后使用 Clientdatabase() 方法,选择数据库。该方法返回的是一个 Database 实例
  • 在 Axum 中,通过 with_state() 来共享 Database 实例
  • ClientDatabase 内部都使用了 Arc,所以它们都是并发安全的,并且可以低成本地进行 Clone 操作

文档定义

和关系型数据库不同,我们需要为 MongoDB 的每个文档进行定义,它由 Databasecollect() 方法提供,它返回的是一个 Collect<T> 结构体。

为了方便操作,我们在共享状态中定义了获取文档的方法:

pub struct AppState {
    pub db: mongodb::Database,
}

impl AppState {
    pub fn collect<T>(&self, name: &str) -> mongodb::Collection<T>
    where
        T: Send + Sync,
    {
        self.db.collection(name)
    }

    pub fn note_collect(&self) -> mongodb::Collection<crate::model::Note> {
        self.collect("note")
    }
}

pub type ArcAppState = std::sync::Arc<AppState>;
  • 共享状态维护了 MongoDB 数据库连接
  • collect<T>() 方法用于从数据库连接中获取集合
    • 调用的其实是 MongoDB 的 Database 提供的同名方法
    • 泛型 T 指向的是我们的模型
    • 参数 name 指向的是集合的名称
  • note_collect() 方法用于从数据库连接中,获取笔记的集合

MongoDB 的 Collection 提供了多种插入文档的方法,其中 insert_one() 用于插入单个文档到集合中:

pub async fn insert(
    State(state): State<ArcAppState>,
    Json(frm): Json<form::CreateNote>,
) -> Result<Json<String>> {
    let note = model::Note::new(frm.title, frm.content);
    state.note_collect().insert_one(&note).await?;
    Ok(Json(note.id))
}

测试:

## 插入单条笔记
POST http://127.0.0.1:9527
Content-Type: application/json

{
    "title":"foo",
    "content":"bar"
}

结果:

⚠️ 在 MongoDB 中,如果数据库、集合不存在,那么在执行插入时,MongoDB 服务器会自动创建。

插入多条记录

MongoDB 还提供了 insert_many() 方法,用于向集合中插入多个文档:

pub async fn insert_many(
    State(state): State<ArcAppState>,
    Json(frm): Json<Vec<form::CreateNote>>,
) -> Result<Json<Vec<String>>> {
    let notes = frm
        .into_iter()
        .map(|f| model::Note::new(f.title, f.content))
        .collect::<Vec<_>>();
    state.note_collect().insert_many(&notes).await?;
    Ok(Json(notes.into_iter().map(|n| n.id).collect()))
}

测试:

## 插入多条笔记
POST http://127.0.0.1:9527/many
Content-Type: application/json

[
    {
        "title":"note1",
        "content":"笔记1"
    },
    {
        "title":"note2",
        "content":"笔记2"
    },
    {
        "title":"note3",
        "content":"笔记3"
    }
]

结果:

HTTP/1.1 200 OK
content-type: application/json
content-length: 70
connection: close
date: Wed, 04 Jun 2025 08:51:18 GMT

[
  "d100i1kdrfaihab7dmig",
  "d100i1kdrfaihab7dmj0",
  "d100i1kdrfaihab7dmjg"
]

main() 函数

要查看完整内容,请先登录