rust简单操作mysql增删查改

4,089 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情

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。

创建表结构

我们先简单创建一个数据表来做示例:

image-20220809115352898.png

构建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));
    }
}

通过以上四个测试方法,我们可以对前面编写的增删查改功能做一个简单测试,基本可以验证我们编写的代码是能正常执行的。