数据库

databases.md
commit - 4d8d53cea59bca095ca5c02ef81f0b1791736855 - 2020.09.12

异步选项

我们有几个示例项目展示了异步数据库适配器的使用:

  • SQLx: https://github.com/actix/examples/tree/master/sqlx_todo
  • Postgres: https://github.com/actix/examples/tree/master/async_pg
  • SQLite: https://github.com/actix/examples/tree/master/async_db

Diesel

当前版本的 Diesel(v1)不支持异步操作,因此使用 web::block 函数是非常重要的,此函数可以将数据库操作加载到 actix 的运行时线程池。

您可以创建操作函数,对应到应用程序对数据库执行的所有操作。

fn insert_new_user(db: &SqliteConnection, user: CreateUser) -> Result<User, Error> {
    use self::schema::users::dsl::*;

    // Create insertion model
    let uuid = format!("{}", uuid::Uuid::new_v4());
    let new_user = models::NewUser {
        id: &uuid,
        name: &user.name,
    };

    // normal diesel operations
    diesel::insert_into(users)
        .values(&new_user)
        .execute(&self.0)
        .expect("Error inserting person");

    let mut items = users
        .filter(id.eq(&uuid))
        .load::<models::User>(&self.0)
        .expect("Error loading person");

    Ok(items.pop().unwrap())
}

您应该使用 r2d2 之类的 crate 来设置数据库池,这使得多个数据库连接可用于您的应用程序。还意味着多个 handler 可以同时操作数据库,并且仍然能够接受新的连接。简单地说,数据库连接池也是应用程序的状态(此种情况下,最好不要使用包裹状态为结构体,因为连接池为您处理共享访问)。

type DbPool = r2d2::Pool<ConnectionManager<SqliteConnection>>;

#[actix_web::main]
async fn main() -> io::Result<()> {
    // Create connection pool
    let pool = r2d2::Pool::builder()
        .build(manager)
        .expect("Failed to create pool.");

    // Start HTTP server
    HttpServer::new(move || {
        App::new::data(pool.clone())
            .resource("/{name}", web::get().to(index))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

在请求 handler 中,使用 Data<T> 提取器从应用程序状态(app state)中获取数据库连接池,然后从中获取连接。这提供了一个可以传递到 web::block 闭包中的数据库连接,并对其具有所有权(owned)。然后,使用必要的参数调用操作函数,最后通过 .await 返回结果。

在示例中,将错误映射到 HttpResponse,然后再使用 ? 运算符。但如果你返回的错误类型实现了 ResponseError trait,则不需要执行此操作。

async fn index(pool: web::Data<DbPool>, name: web::Path<(String)>) -> impl Responder {
    let name = name.into_inner();

    let conn = pool.get().expect("couldn't get db connection from pool");

    let user = web::block(move || actions::insert_new_user(&conn, &user))
        .await
        .map_err(|e| {
            eprintln!("{}", e);
            HttpResponse::InternalServerError().finish()
        })?;
    
    Ok(HttpResponse::Ok().json(user))
}

请参见此处的完整示例:https://github.com/actix/examples/tree/master/diesel