在hello_salvo中,我们已经解决了如何解析请求数据的问题。接下来我们需要解决如何通过rust对数据库进行操作的问题。我们先从最简单的增删查改开始:
依赖
[dependencies] # 序列化工具 serde = { version = "1.0.140", features = ["derive"] } # 一次性初始化对象工具包 once_cell = "1.13.0" #数据库依赖 mysql = "20.0.0" #处理时间 chrono = "0.4.19" 复制代码
当前的示例是使用rust操作mysql数据库,所以我们主要的依赖是mysql
,其他的基本都是辅助性的crate。
创建表结构
我们先简单创建一个数据表来做示例:
构建Account结构体
我们在创建好表结构后,需要在rust项目中创建与之对应的结构体,以便后续CRUD的时候围绕这个结构体来操作数据表记录。
use chrono::NaiveDateTime; use serde::Serialize; #[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct Account { pub id: String, pub account: String, pub password: String, pub enabled: i32, pub create_time: NaiveDateTime, pub modify_time: NaiveDateTime, } 复制代码
构建链接池
use mysql::{Pool, PooledConn}; use once_cell::sync::OnceCell; use tracing::{instrument, info}; // 创建一个全局的DB_POOL,可以一直使用,启动的时候初始化即可 static DB_POOL: OnceCell<Pool> = OnceCell::new(); // 初始化数据库链接池 #[instrument] pub fn init_mysql_pool(db_url: &str) { info!("初始化数据库线程池--------开始-------"); DB_POOL.set(mysql::Pool::new(&db_url).expect(&format!("Error connecting to {}", &db_url))) .unwrap_or_else(|_| { info!("try insert pool cell failure!") }); info!("初始化数据库线程池--------结束-------"); } // 从链接链接池里面获取链接 #[instrument] pub fn get_connect() -> PooledConn { info!("从链接池获取数据库链接----------开始----------"); let conn = DB_POOL.get().expect("Error get pool from OneCell<Pool>").get_conn().expect("Error get_connect from db pool"); info!("从链接池获取数据库链接----------结束----------"); conn } 复制代码
我们使用了OnceCell的方式构建了一个链接池,这个链接池只允许被get_connect
方法使用,DB_POOL只会在项目启动的时候被初始化一次,初始化成功后,我们就可以一直调用DB_POOL获取数据库链接了。
CRUD操作
- 查询
pub fn get_by_id(id: &str) -> Option<Account> { // 获取数据库链接 let mut conn = get_connect(); // 根据id查询账号信息 let query_result = conn.exec_first("select id,account,password,enabled,create_time,modify_time from account where id=:id", params!("id"=>id)) .map(|row| { row.map(|(id, account, password, enabled, create_time, modify_time)| Account { id, account, password, enabled, create_time, modify_time }) }); // 判断是否查询到数据 match query_result { Ok(result) => { result } Err(_) => { None } } } 复制代码
1.链接池在项目启动的时候已经构建完毕,
init_mysql_pool
方法可以在main函数中直接调用,所以我们在数据库操作的时候可以直接调用get_connect
方法获取数据库链接;2.通过conn直接调用
exec_first
传递sql语句和对应的参数值;3.返回值通过map方法解析拿到的row结果集,在里面直接构建Account对象并返回;
4.判断拿到的结果集是否有数据,有的话直接返回;报错的话,返回None;
- 插入
pub fn insert(account: &str, password: &str) -> Result<u64, GlobalError> { // 获取数据库链接 let mut conn = get_connect(); // 生成主键id let id = utils::generate_id()?; // 执行插入语句,目前id写死,后续会修改 let x = match "insert into account (id,account,password,enabled,create_time,modify_time) values (?,?,?,1,now(),now())" .with((id, account, password)) .run(&mut conn) { // 返回受影响的数据行数 Ok(res) => { Ok(res.affected_rows()) } Err(e) => { Err(GlobalError::new(200, "创建账号失败", e.to_string().as_str())) } }; x } 复制代码
1.现获取数据库链接;
2.准备好主键id值;这里没有使用数据库自增主键,小伙伴们可以自行使用;
3.编写sql语句,通过
with
方法传递参数值,里面传递元组;4.通过
run
方法把数据库链接传递进去;5.判断返回结果值;
res.affected_rows()
是获取受影响的行数;
pub fn delete_by_id(id: &str) -> Result<u64, GlobalError> { let mut conn = get_connect(); let x = match "DELETE FROM account WHERE id=?" .with((id, )) .run(&mut conn) { Ok(res) => { Ok(res.affected_rows()) } Err(e) => { Err(GlobalError::new(200, "删除用户失败", e.to_string().as_str())) } }; x } 复制代码
1.获取数据库链接;
2.编写删除的sql语句;
3.传递参数值;需要注意的是,因为只有一个参数值,所以传递的元组需要加上逗号才能正确解析,否则报错;比如
(id,)
代表只有一个参数值的元组;4.判断返回值是否正常;
pub fn update(account: Account) -> Result<u64, GlobalError> { let mut conn = get_connect(); let x = match "UPDATE account SET account=?, password=?,enabled=?, modify_time=now() where id=?".with((&account.account, &account.password, &account.enabled, &account.id)).run(&mut conn) { Ok(res) => { Ok(res.affected_rows()) } Err(e) => { Err(GlobalError::new(200, "用户信息更新失败", e.to_string().as_str())) } }; x } 复制代码
1.获取数据库链接;
2.编写更新的sql语句;
3.通过
with
方法传递参数值,并使用run
方法传递数据库链接执行sql语句;5.判断返回值是否正常;
测试
#[cfg(test)] mod test { use chrono::NaiveDateTime; use crate::dao::account_mapper::AccountMapper; use crate::dao::po::account::Account; use crate::dao; // 测试查询功能 #[test] pub fn get_by_id_test() { // 初始化数据库链接 dao::init(); // 执行查询 let res = AccountMapper::get_by_id("1"); // 验证查询结果 assert_eq!(res, Some(Account { id: String::from("1"), account: String::from("zouwei"), password: String::from("123456"), enabled: 1, create_time: NaiveDateTime::parse_from_str("2022-07-28 17:08:19", "yyyy-MM-dd HH:mm:ss").unwrap(), modify_time: NaiveDateTime::parse_from_str("2022-07-28 17:08:19", "yyyy-MM-dd HH:mm:ss").unwrap(), })); } // 测试添加账号 #[test] pub fn insert_test() { // 初始化数据库链接池 dao::init(); // 添加账号 let res = AccountMapper::insert("zouwei", "098765"); // 校验结果 assert_eq!(res, Ok(1)); } // 测试删除功能 #[test] pub fn delete_by_id_test() { // 初始化数据库链接 dao::init(); // 根据主键删除数据 let res = AccountMapper::delete_by_id("1"); // 验证结果 assert_eq!(res, Ok(1)); } #[test] pub fn update_test() { // 初始化数据库链接池 dao::init(); // 获取当前时间 let data_time = chrono::offset::Utc::now(); // 更新用户信息 let res = AccountMapper::update(Account { id: String::from("2"), account: String::from("zouwei"), password: String::from("123456"), enabled: 1, create_time: NaiveDateTime::from_timestamp(data_time.timestamp(), 0), modify_time: NaiveDateTime::from_timestamp(data_time.timestamp(), 0), }); // 验证结果 assert_eq!(res, Ok(1)); } } 复制代码
通过以上四个测试方法,我们可以对前面编写的增删查改功能做一个简单测试,基本可以验证我们编写的代码是能正常执行的。