携手创作,共同成长!这是我参与「掘金日新计划 · 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。
创建表结构
我们先简单创建一个数据表来做示例:
构建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;
扫描二维码关注公众号,回复: 14459816 查看本文章
- 插入
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));
}
}
复制代码
通过以上四个测试方法,我们可以对前面编写的增删查改功能做一个简单测试,基本可以验证我们编写的代码是能正常执行的。