未阅读前篇的小伙伴可以先阅读第一篇,以了解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
评论区
写评论牛啊 楼主,你这必须要对java和Rust很熟悉才行吧,我只是小菜鸟 只能仰望了 先start了
感谢支持
--
👇
malefooo: 顶一下,作者太顶了
感谢支持
--
👇
ncq1993: 持续关注,已start,作者辛苦了。
目前 1.2.6版本可以用于生产。如果有更新 推荐使用最新版本
👇
chinagxwei: 不知道基础功能是否完善?该版本是否可以用于生产?
持续关注,已start,作者辛苦了。
不知道基础功能是否完善?该版本是否可以用于生产?
顶一下,作者太顶了