未閱讀前篇的小夥伴可以先閱讀第一篇,以瞭解Rbatis的心路歷程
第一篇地址
Github原始碼連結rbatis
下面開始
第一篇我們講到了,已經設計完了基本的ORM主體框架,這次帶來的有
Wrapper,分頁外掛,邏輯刪除外掛
1 第一步 設計Wrapper。所謂Wrapper簡單的說就是基本sql where語法的封裝,可以在程式碼中直接new出來避免大量sql出現。
舉個例子:
/// let w = Wrapper::new(&DriverType::Mysql)
/// .eq("id", 1)
/// .and()
/// .ne("id", 1)
/// .and()
/// .in_array("id", &[1, 2, 3])
/// .and()
/// .not_in("id", &[1, 2, 3])
/// .and()
/// .like("name", 1)
/// .or()
/// .not_like("name", "asdf")
/// .and()
/// .between("create_time", "2020-01-01 00:00:00", "2020-12-12 00:00:00")
/// .group_by(&["id"])
/// .order_by(true, &["id", "name"])
/// .check().unwrap();
我們要注意的是,設計Wrapper 必須帶有語法檢查,例如group_by和order_by關鍵字 在拼接的時候,必須檢查前面的語法是否 以 where 結尾,如果是where結尾那麼我們要刪掉它,否則語法錯誤。
2 第二步,設計分頁外掛.分頁外掛會自動分析你寫的sql或者wrapper,自動把sql語句拆分為 count語句計算總數和select語句篩選資料。
首先定義介面:
pub trait PagePlugin: Send + Sync {
/// return 2 sql for select , (count_sql,select_sql)
fn create_page_sql(&self, driver_type: &DriverType, tx_id: &str, sql: &str, args: &Vec<serde_json::Value>, page: &dyn IPageRequest) -> Result<(String, String), rbatis_core::Error>;
}
定義Ipage抽象介面
pub trait IPage<T>: IPageRequest {
fn get_records(&self) -> &Vec<T>;
fn get_records_mut(&mut self) -> &mut Vec<T>;
fn set_records(&mut self, arg: Vec<T>);
///計算總頁碼數 pages
fn get_pages(&self) -> u64 {
if self.get_size() == 0 {
return 0;
}
let mut pages = self.get_total() / self.get_size();
if self.get_total() % self.get_size() != 0 {
pages = pages + 1;
}
return pages;
}
///sum offset 計算 開始的頁碼索引值
fn offset(&self) -> u64 {
if self.get_current() > 0 {
(self.get_current() - 1) * self.get_size()
} else {
0
}
}
}
定義Page物件
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Page<T> {
///data
pub records: Vec<T>,
///total num
pub total: u64,
///default 10
pub size: u64,
///current index
pub current: u64,
pub serch_count: bool,
}
實現分頁邏輯,就是把 單條sql 拆分為 count操作和select操作執行,最後返回Page物件
impl PagePlugin for RbatisPagePlugin {
fn create_page_sql<>(&self, driver_type: &DriverType, tx_id: &str, sql: &str, args: &Vec<Value>, page: &dyn IPageRequest) -> Result<(String, String), rbatis_core::Error> {
let mut sql = sql.to_owned();
sql = sql.replace("select ", "SELECT ");
sql = sql.replace("from ", "FROM ");
sql = sql.trim().to_string();
let limit_sql = driver_type.page_limit_sql(page.offset(), page.get_size())?;
sql = sql + limit_sql.as_str();
if !sql.starts_with("SELECT ") && !sql.contains("FROM ") {
return Err(rbatis_core::Error::from("[rbatis] xml_fetch_page() sql must contains 'select ' And 'from '"));
}
let mut count_sql = sql.clone();
if page.is_serch_count() {
//make count sql
let sql_vec: Vec<&str> = count_sql.split("FROM ").collect();
count_sql = "SELECT count(1) FROM ".to_string() + sql_vec[1];
}
return Ok((count_sql, sql));
}
}
最後,抽象外掛定義在Rbatis成員中
/// rbatis engine
pub struct Rbatis<'r> {
...
/// page plugin,動態型別的外掛
pub page_plugin: Box<dyn PagePlugin>
}
我們使用分頁的時候就變成了
let w = Wrapper::new(&rb.driver_type().unwrap())
.eq("delete_flag",1)
.check().unwrap();
let r: Page<BizActivity> = rb.fetch_page_by_wrapper(&w, &PageRequest::new(1, 20)).await.unwrap();
//執行結果
2020-07-05T23:38:16.348674800+08:00 INFO rbatis::rbatis - [rbatis] Query ==> SELECT count(1) FROM biz_activity WHERE delete_flag = 1 AND delete_flag = ? LIMIT 0,20
2020-07-05T23:38:16.350675400+08:00 INFO rbatis::rbatis - [rbatis] Args ==> [1]
2020-07-05T23:38:16.370671900+08:00 INFO rbatis::rbatis - [rbatis] Total <== 1
2020-07-05T23:38:16.370671900+08:00 INFO rbatis::rbatis - [rbatis] Query ==> SELECT create_time,delete_flag,h5_banner_img,h5_link,id,name,pc_banner_img,pc_link,remark,sort,status,version FROM biz_activity WHERE delete_flag = 1 AND delete_flag = ? LIMIT 0,20
2020-07-05T23:38:16.370671900+08:00 INFO rbatis::rbatis - [rbatis] Args ==> [1]
2020-07-05T23:38:16.373696300+08:00 INFO rbatis::rbatis - [rbatis] Total <== 5
{
"records": [{
"id": "12312",
"name": "null",
"pc_link": "null",
"h5_link": "null",
"pc_banner_img": "null",
"h5_banner_img": "null",
"sort": "null",
"status": 1,
"remark": "null",
"create_time": "2020-02-09 00:00:00 UTC",
"version": 1,
"delete_flag": 1
}],
"total": 5,
"size": 20,
"current": 1,
"serch_count": true
}
3 設計 邏輯刪除外掛
定義介面
/// Logic Delete Plugin trait
pub trait LogicDelete: Send + Sync {
//邏輯刪除的資料庫欄位
fn column(&self) -> &str;
//刪除標誌
fn deleted(&self) -> i32;
fn un_deleted(&self) -> i32;
//建立刪除時生成的sql
fn create_sql(&self, driver_type: &DriverType, table_name: &str, sql_where: &str) -> Result<String, rbatis_core::Error>;
}
配合wrapper攔截刪除操作改為update 操作
async fn remove_by_id<T>(&self, id: &T::IdType) -> Result<u64> where T: CRUDEnable {
let mut sql = String::new();
if self.logic_plugin.is_some() {
sql = self.logic_plugin.as_ref().unwrap().create_sql(&self.driver_type()?, T::table_name().as_str(), format!(" WHERE id = {}", id).as_str())?;
} else {
sql = format!("DELETE FROM {} WHERE id = {}", T::table_name(), id);
}
return self.exec_prepare("", sql.as_str(), &vec![]).await;
}
最後使用的時候變成了
let mut rb = Rbatis::new();
rb.link("mysql://root:123456@localhost:3306/test").await.unwrap();
//設定 邏輯刪除外掛
rb.logic_plugin = Some(Box::new(RbatisLogicDeletePlugin::new("delete_flag")));
//執行邏輯刪除
let r = rb.remove_by_id::<BizActivity>(&"1".to_string()).await;
if r.is_err() {
println!("{}", r.err().unwrap().to_string());
}
返回結果
2020-07-05T23:22:51.235834600+08:00 INFO rbatis::rbatis - [rbatis] Exec ==> UPDATE biz_activity SET delete_flag = 0 WHERE id = 1
2020-07-05T23:18:34.426681800+08:00 INFO rbatis::rbatis - [rbatis] Total <== 1
最後,我們的框架基本功能已經完善,剩下的就是完善文件以及投入生產環境使用啦。慢慢享受rust帶來的穩定和高效能~
本作品採用《CC 協議》,轉載必須註明作者和本文連結