本文介绍第二部分,数据库及 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 可以看以前写过的一篇文章,https://blog.hitol.top/16669473309292.html
本篇文章主要介绍一下 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 自己手写……
