You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建兼容Sqlx连接池与事务的Actix-web服务器?

错误原因分析

你遇到的两个编译错误都是因为泛型约束不符合sqlx和Actix Web的类型要求:

  1. Pool<Postgres>: Executor<'_> 未满足:在sqlx 0.4.x中,PgPool本身并没有实现Executor trait,只有引用类型&PgPool才实现了这个trait。
  2. Pool<Postgres>: Copy 未满足:PgPool是包含数据库连接资源的类型,内部通过Arc管理共享,但它本身不能被Copy(复制会导致资源管理混乱,所以sqlx没有为它实现Copy trait)。

解决方案(适配sqlx 0.4.x)

我们可以通过调整create_server的设计,让它同时支持生产环境的连接池和测试环境的事务,具体修改如下:

1. 修改create_server的泛型设计

将泛型参数改为接受一个工厂闭包,用于为每个worker提供数据库执行器实例。这样生产环境可以返回连接池的克隆,测试环境可以返回事务的所有权(因为测试服务器只需要一个worker):

pub fn create_server<F, E>(
    listener: TcpListener,
    db_factory: F,
) -> Result<Server, std::io::Error>
where
    F: Fn() -> E + Clone + 'static,
    E: 'static,
    // 要求任意生命周期下,E的引用都能作为Postgres的Executor
    for<'a> &'a E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
    let server = HttpServer::new(move || {
        App::new()
            .data(db_factory()) // 每个worker调用工厂函数获取实例
            .route("/", web::get().to(index))
    })
    .listen(listener)?
    .run();
    Ok(server)
}

2. 调整生产环境的server函数

传递一个返回PgPool克隆的闭包(PgPool的克隆是轻量的,因为内部是Arc):

pub async fn server(pool: PgPool) -> std::io::Result<()> {
    const PORT: usize = 8088;
    let listener = TcpListener::bind(format!("0.0.0.0:{}", PORT)).expect("Failed to create listener");
    println!("Running on port {}", PORT);
    // 工厂闭包返回pool的克隆
    create_server(listener, move || pool.clone()).unwrap().await
}

3. 修改测试代码,使用单worker服务器

测试中,事务无法被克隆,所以我们设置服务器只运行一个worker,让工厂闭包只执行一次,直接返回事务的所有权:

#[cfg(test)]
pub mod tests {
    use super::*;
    use std::net::TcpListener;

    #[actix_rt::test]
    async fn test_foo() {
        let pool = create_pool().await;
        let transaction = pool.begin().await.expect("Failed to create transaction");
        let listener = TcpListener::bind("0.0.0.0:0").expect("Failed to create listener");
        
        let server = create_server(listener, move || transaction)
            .expect("Failed to create server");
        
        // 启动服务器
        tokio::spawn(server);
        
        // 这里可以添加测试逻辑,比如用reqwest调用服务器的/api路径
        // 测试完成后,事务会自动回滚,无需手动提交
    }
}

更简单的替代方案:升级sqlx到0.5+

如果可以升级sqlx版本到0.5.x,PgPool会直接实现Executor trait,同时我们可以简化泛型约束:

  1. 修改Cargo.toml中的sqlx依赖:
sqlx = { version = "0.5.13", default-features = false, features = ["postgres", "runtime-async-std-native-tls"] }
  1. 修改create_server的泛型约束:
pub fn create_server<E>(
    listener: TcpListener,
    db: E,
) -> Result<Server, std::io::Error>
where
    E: Clone + 'static,
    for<'a> E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
    let server = HttpServer::new(move || {
        App::new()
            .data(db.clone())
            .route("/", web::get().to(index))
    })
    .listen(listener)?
    .run();
    Ok(server)
}
  1. 测试代码中,设置服务器为单worker(因为事务无法克隆):
#[actix_rt::test]
async fn test_foo() {
    let pool = create_pool().await;
    let transaction = pool.begin().await.expect("Failed to create transaction");
    let listener = TcpListener::bind("0.0.0.0:0").expect("Failed to create listener");
    
    // 手动创建服务器并设置1个worker,避免需要克隆事务
    let server = HttpServer::new(move || {
        App::new()
            .data(transaction)
            .route("/", web::get().to(index))
    })
    .workers(1) // 关键:只启动一个worker,无需克隆事务
    .listen(listener)
    .expect("Failed to create server")
    .run();
    
    tokio::spawn(server);
}

这样修改后,生产环境和测试环境的代码都能正常编译,同时满足你“测试时使用独立事务,生产时使用连接池”的需求。

内容的提问来源于stack exchange,提问作者Oskar Persson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:07:55