< 返回版块

zhuxiujia 发表于 2020-07-05 23:23

Tags:制作Rust语言异步ORM框架(Mybatis)第二弹

未阅读前篇的小伙伴可以先阅读第一篇,以了解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 <== 1
{
	"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带来的稳定和高性能~~~


Ext Link: https://github.com/rbatis/rbatis

评论区

写评论
xjy12345654 2020-07-13 08:24

牛啊 楼主,你这必须要对java和Rust很熟悉才行吧,我只是小菜鸟 只能仰望了 先start了

作者 zhuxiujia 2020-07-06 22:47

感谢支持

--
👇
malefooo: 顶一下,作者太顶了

作者 zhuxiujia 2020-07-06 22:47

感谢支持

--
👇
ncq1993: 持续关注,已start,作者辛苦了。

作者 zhuxiujia 2020-07-06 20:51

目前 1.2.6版本可以用于生产。如果有更新 推荐使用最新版本

👇
chinagxwei: 不知道基础功能是否完善?该版本是否可以用于生产?

ncq1993 2020-07-06 10:12

持续关注,已start,作者辛苦了。

chinagxwei 2020-07-06 09:16

不知道基础功能是否完善?该版本是否可以用于生产?

malefooo 2020-07-06 08:12

顶一下,作者太顶了

1 共 7 条评论, 1 页