在rust/diesel应用程序中使用r2d2实现连接池

4

我正在尝试在 Rust/Diesel/Rocket 应用程序中实现连接池。我不确定如何确保只调用 establish_pooled_connection() 方法一次,以准备连接池。

以下是我的代码。

来自 lib.rs:

pub fn establish_pooled_connection() -> PooledConnection<ConnectionManager<PgConnection>> {
    dotenv().ok();

    let database_url = env::var("DB_URL")
        .expect("DATABASE_URL must be set");

    let manager = ConnectionManager::<PgConnection>::new(&database_url);
    let pool = r2d2::Pool::builder().build(manager).expect("Failed to create pool.");
    let conn = pool.clone().get().unwrap();
    return conn;
}

main.rs文件中,我使用了上述方法:

#[get("/", format = "json")]
fn find_all() -> Json<Vec<Person>> {
    let connection = establish_pooled_connection();

    let all: QueryResult<Vec<Person>> = person::table().get_results(&connection);
    ...

这里的问题是每个get方法(如上所示)都调用了"establish_pooled_connection()",而且每次都重新实例化...
我来自Java领域,依赖注入可以避免重新实例化。
在Rust/Diesel应用程序中实现连接池的正确方法是什么?
2个回答

2
问题在于每个get方法(例如上面)都调用establish_pooled_connection(),这样就会重新实例化一切...
我也曾经遇到过设置池连接的问题,所以我想留下我所做的事情。
我成功地在rocket/diesel应用程序中使用了池连接,通过将已建立的池添加到服务器状态并从各个函数通过该服务器状态访问数据库来实现。
// add the `r2d2` feature for diesel 
use diesel::{
    r2d2::{ConnectionManager, Pool, PooledConnection},
    MysqlConnection,
};

// set an alias, so we don't have to keep writing out this long type
pub type DbPool = Pool<ConnectionManager<MysqlConnection>>;

// a real-world app would have more fields in the server state like 
// CORS options, environment values needed for external APIs, etc.
pub struct ServerState {
  pub db_pool: DbPool
}

Rocket允许我们在使用build()构建服务器时,通过.manage()调用来定义服务器状态。
#[rocket::main]
pub async fn main() {
  let db_pool: DbPool = establish_connection_pool();

  rocket::build()
    .mount("/", routes![test]) // mount routes
    .manage(ServerState { db_pool })
    .launch()
    .await
    .expect("Failed to launch rocket server.")
}

因为Rocket允许我们从函数中检索服务器状态,只要它们具有Rocket的宏(getpost等),因此我们可以从服务器状态中检索数据库。
use rocket::{get, serde::json::Json, State};

#[get("/test?<user_id>")]
pub async fn test(
  state: &State<ServerState>,
  user_id: String
) -> Result<Json<User>, Error> {
  let pooled = &state.db_pool.get()?;

  pooled.transaction(|| {
    // some diesel query
  })
}

我在处理此事时所用的版本如下。
diesel = { version = "1.4.8", features = ["r2d2", "mysql", "chrono"] }
rocket = { version = "0.5.0-rc.1", features = ["json"] }

需要考虑的其他事项

有些人希望像我上面所做的那样直接使用r2d2建立连接池。我个人认为,将连接池作为服务器状态中的字段传递,可以更好地控制编写集成测试。但是,还有其他处理连接池的方法值得您了解。

Rocket提供了自己的解决方案来处理池化数据库连接,分别是rocket_sync_db_pools[1]和rocket_db_pools[2]。如果您使用ORM(如diesel[3]和sqlx[4])与库中涵盖的数据库一起使用,则强烈建议您研究它们。

只需在项目的根目录中设置一个Rocket.toml文件即可。

[global.databases]
test_db = { url = "mysql://mysql:password@localhost:5432/test_db_name" }

现在,通过发挥 database 宏的威力,您可以轻松建立连接。

#[macro_use]
extern crate rocket_sync_db_pools;

// as long as we set the `Rocket.toml` to include information about 
// what database we want to point to, the `database` macro does 
// the magic for us
#[database("test_db")]
pub struct TestDbConn(diesel::MysqlConnection);

// We can directly access the pooled connection as we did with the server state
#[get("/test?<user_id>")]
pub async fn test(
  conn: TestDbConn,
  user_id: String
) -> Result<Json<User>, Error> {
  conn.run(move || {
    // some diesel query
  })
}

[1] 对于像diesel这样的同步ORM,请使用rocket_sync_db_poolshttps://api.rocket.rs/v0.5-rc/rocket_sync_db_pools/index.html#

[2] 对于像deadpool-postgressqlx这样的异步ORM,请使用rocket_db_poolshttps://api.rocket.rs/v0.5-rc/rocket_db_pools/index.html#

[3] https://api.rocket.rs/v0.5-rc/rocket_sync_db_pools/index.html#database-support

[4] https://api.rocket.rs/v0.5-rc/rocket_db_pools/index.html#supported-drivers


2
您不应该在每个处理程序中创建新的连接,而是使用框架中的一些状态共享机制。
请参阅此处以获取有关如何在 Rocket 中使用状态的指南,这将是一个良好的起点。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接