製作 Rust 語言非同步 ORM 框架(Mybatis)第二彈

daxiaxia發表於2020-07-05

未閱讀前篇的小夥伴可以先閱讀第一篇,以瞭解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 協議》,轉載必須註明作者和本文連結

相關文章