Rust web 开发-2. 数据库/orm

884 阅读4分钟

本系列文章从以下几个方面学习如何使用 Rust 进行 web 开发。

  1. web 框架
  2. 数据库/orm
  3. config
  4. log
  5. 线程池
  6. kafka
  7. redis
  8. 打包成 docker 镜像 ……

本文介绍第二部分,数据库及 orm。 主要介绍 Rust 如何使用 sqlx 连接 MySQL,orm 选用了 rbatis。

准备 MySQL 数据库,简单的一个表结构

CREATE TABLE `user` (
	`id` bigint NOT NULL AUTO_INCREMENT,
	`user_name` varchar(32) DEFAULT NULL,
	`password` varchar(64) DEFAULT NULL,
	PRIMARY KEY (`id`)
);

接下来分别使用 sqlx 和 rbatis 进行 CRUD 操作。

sqlx

添加依赖

sqlx = { version = "0.7.2", features = [
    "runtime-async-std-native-tls",
    "mysql",
    "chrono",
] }

sqlx 连接数据库的链接格式:mysql://{user}:{password}@{IP}:{port}/{database name} 例如我本地的数据库地址:mysql://root:12345678@localhost:3306/rust_web

连接 MySQL

use sqlx::{mysql::MySqlPoolOptions, MySql, Pool};

pub async fn get_mysql_pool() -> Pool<MySql> {
    let database_url = "mysql://root:12345678@localhost:3306/rust_web";
    MySqlPoolOptions::new().connect(database_url).await.unwrap()
}

api

use actix_web::{get, post, web, HttpResponse, Scope};
use serde::{Deserialize, Serialize};

use crate::{db::get_mysql_pool, success};

// 定义路由,对外暴露的接口
pub fn routes() -> Scope {
    web::scope("/users")
        .service(add_user)
        .service(query_user)
        .service(update_user)
        .service(delete_user)
}

// 定义实体
#[derive(Debug, Deserialize, Clone, Serialize, Default,sqlx::FromRow)]
#[serde(rename_all = "camelCase")]
pub struct User {
    pub id: Option<i64>,
    pub user_name: Option<String>,
    pub password: Option<String>,
}

#[post("/add")]
pub async fn add_user(user: web::Json<User>) -> HttpResponse {
    let user = user.to_owned();
    let id = save_user(user).await.unwrap();
    success(Some(id))
}
// insert
async fn save_user(user: User) -> Result<u64, sqlx::Error> {
    // 获取数据库连接池
    let pool = get_mysql_pool().await;
    let sql = "insert into user(user_name, password) values (?,?)";
    let result = sqlx::query(sql)
        .bind(user.user_name)
        .bind(user.password)
        .execute(&pool)
        .await?;
    let id = result.last_insert_id();
    Ok(id)
}

// update
#[post("/update/{id}")]
pub async fn update_user(id: web::Path<i64>, user: web::Json<User>) -> HttpResponse {
    let user = user.to_owned();

    let sql = "update user set user_name =?, password =? where id = ?";
    sqlx::query(sql)
        .bind(user.user_name)
        .bind(user.password)
        .bind(id.into_inner())
        .execute(&get_mysql_pool().await)
        .await.unwrap();
    success(Some(1))
}

// select
#[get("/query/{id}")]
pub async fn query_user(id:web::Path<i64>) -> HttpResponse {
    let sql = "select * from user where id = ?";
    let result = sqlx::query_as::<_,User>(sql).bind(id.into_inner()).fetch_optional(&get_mysql_pool().await).await.unwrap();
    success(result)
}

// delete
#[post("/delete/{id}")]
pub async fn delete_user(id:web::Path<i64>) -> HttpResponse {
    let sql = "delete from user where id = ?";
    sqlx::query(sql).bind(id.into_inner()).execute(&get_mysql_pool().await).await.unwrap();
    success(Some(1))
}

服务启动之后测试: 新增 user:

curl -X "POST" "http://localhost:8099/users/add" \
     -H 'token: 1' \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "password": "3456789",
  "userName": "张三"
}'

查询 user:

curl "http://localhost:8099/users/query/1" \
     -H 'token: 1'

修改 user:

curl -X "POST" "http://localhost:8099/users/update/1" \
     -H 'token: 1' \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "password": "987654",
  "userName": "lisi"
}'

删除 user:

curl -X "POST" "http://localhost:8099/users/delete/2" \
     -H 'token: 1'

orm

orm 目前我使用过的有两个,一个是 Diesel,一个是 rbatis。

Diesel 可以看以前写过的一篇文章,blog.hitol.top/16669473309… 本篇文章主要介绍一下 rbatis。

添加依赖

# rbatis
rbs = { version = "4.3" }
rbatis = { version = "4.3" }
rbdc-mysql = { version = "4.3" }

创建 rbatis

use rbatis::RBatis;
use rbdc_mysql::MysqlDriver;

pub async fn get_rb() -> RBatis {
    let database_url = "mysql://root:12345678@localhost:3306/rust_web";
    let rb = RBatis::new();
    rb.init(MysqlDriver {}, database_url).unwrap();
    rb
}

在 User 实体定义的文件中调用 crud! 宏。

crud!(User {});//crud = insert+select_by_column+update_by_column+delete_by_column

宏调用之后就可以直接使用 User::insert、User::update、User::select、User::delete。

#[derive(Debug, Deserialize, Clone, Serialize, Default, sqlx::FromRow)]
#[serde(rename_all = "camelCase")]
pub struct User {
    pub id: Option<i64>,
    pub user_name: Option<String>,
    pub password: Option<String>,
}

crud!(User {}); // crud

#[post("/add")]
pub async fn add_user(user: web::Json<User>) -> HttpResponse {
    let user = user.to_owned();
    // let id = save_user(user).await.unwrap();
    let id = save_user_rb(user).await;
    success(Some(id))
}

async fn save_user_rb(user: User) -> i64 {
    let rbatis = get_rb().await;
    let x = User::insert(&rbatis, &user).await;
    let x = x.unwrap();
    i64::from(x.last_insert_id)
}

//update: User::update_by_column(&get_rb().await, &user, "id").await;
//select: User::select_by_column(&get_rb().await, "id",id.into_inner()).await.unwrap();
//delete: User::delete_by_column(&get_rb().await, "id", id.into_inner()).await;

使用了 serde::rename_all 将实体中的字段格式从下划线转为了驼峰类型,然而使用 rbaits 会有问题,打印出的 sql 中字段也是驼峰的类型。需要注意的是本文中代码仅为演示使用,偷了个懒,数据库实体跟 VO 层用的是一个 struct 实体。在实际生产代码中应是两个实体,通过实现 From/into 转换为数据库实体即可。

数据库连接池初始化

需要注意点是数据库连接池现在是每次调用都会初始化,为了演示连接数据库。要改为只初始化一次,可以使用lazy_static宏来延迟初始化静态变量。lazy_static宏允许在首次访问变量时执行初始化代码。

use lazy_static::*;
use rbatis::RBatis;
use rbdc_mysql::MysqlDriver;
use sqlx::{mysql::MySqlPoolOptions, MySql, Pool};
use std::{sync::Mutex, time::Duration};

lazy_static! {
    static ref MYSQL_POOL: Mutex<Vec<Pool<MySql>>> = Mutex::new(vec![]);
}

lazy_static! {
    static ref RB: Mutex<RBatis> = Mutex::new(RBatis::new());
}

pub async fn init_db(database_url: String) {
    let pool = MySqlPoolOptions::new()
        .max_connections(20)
        .min_connections(5)
        .idle_timeout(Some(Duration::from_secs(30)))
        .connect(&database_url)
        .await
        .unwrap();

    let mut pools = MYSQL_POOL.lock().unwrap();
    (*pools).push(pool);

    let rbatis = RBatis::new();
    rbatis.init(MysqlDriver {}, &database_url).unwrap();
    let mut rb = RB.lock().unwrap();
    *rb = rbatis;

}

pub async fn get_mysql_pool() -> Pool<MySql> {
    let pools = MYSQL_POOL.lock().unwrap();
    unsafe { (*pools).get_unchecked(0).to_owned() }
}

pub async fn get_rb() -> RBatis {
    RB.lock().unwrap().clone()
}

本篇文章代码在 github

我在项目中用的话一般是 sqlx + rbatis 一起使用,简单的 sql 用 rbatis 自动生成的,复杂一些的 sql 就使用 sqlx 自己手写……

欢迎关注微信公众号