167 个稳定版本
4.5.29 | 2024 年 7 月 4 日 |
---|---|
4.5.26 | 2024 年 6 月 30 日 |
4.5.21 | 2024 年 3 月 14 日 |
4.5.10 |
|
1.0.7 | 2020 年 2 月 29 日 |
#107 在 数据库接口
每月 2,856 次下载
用于 28 个 Crates (20 直接)
640KB
13K SLoC
一种编译时代码生成 ORM,在易用性、性能和健壮性之间取得平衡
它是一个 ORM、一个小型编译器、一个动态 SQL 语言
- 高性能:编译时 动态 SQL,基于 Future/Tokio,连接池
- 可靠性:Rust 安全代码,预编译:
#{arg}
,直接替换:${arg}
,统一?
占位符(支持所有驱动程序) - 生产力:强大的 拦截器接口、CRUD、py_sql、html_sql、表同步插件、abs_admin、rbdc-drivers
- 可维护性:RBDC 驱动程序支持自定义驱动程序、自定义连接池,支持第三方驱动程序包
感谢 SQLX, deadpool,mobc, Tiberius, MyBatis, xorm
等参考设计或代码实现。V4 版本的发布受到了这些框架的灵感和支持。**
性能
- 此基准测试是 MockTable、MockDriver、MockConnection,假设网络 I/O 时间为 0
- 运行代码
rbatis.query_decode::<Vec<i32>>("", vec![]).await;
在 benches bench_raw() 上运行 - 运行代码
MockTable::insert(&rbatis,&t).await;
在 benches bench_insert() 上运行 - 运行代码
MockTable::select_all(&rbatis).await.unwrap();
在 benches bench_select() 上运行 - 查看 bench 代码
---- bench_raw stdout ----(windows/SingleThread)
Time: 52.4187ms ,each:524 ns/op
QPS: 1906435 QPS/s
---- bench_select stdout ----(macos-M1Cpu/SingleThread)
Time: 112.927916ms ,each:1129 ns/op
QPS: 885486 QPS/s
---- bench_insert stdout ----(macos-M1Cpu/SingleThread)
Time: 346.576666ms ,each:3465 ns/op
QPS: 288531 QPS/s
由 Workflows CI 支持的操作系统/平台
- Rust 编译器版本 v1.75+ 及以后版本
平台 | 是支持的 |
---|---|
Linux(unbutu latest***) | √ |
Apple/MacOS(latest) | √ |
Windows(latest) | √ |
支持的数据结构
数据结构 | 是支持的 |
---|---|
Option |
√ |
Vec |
√ |
HashMap |
√ |
i32,i64,f32,f64,bool,String ...更多 Rust 基础类型 |
√ |
rbatis::rbdc::类型::{Bytes,Date,DateTime,Time,Timestamp,Decimal,Json} |
√ |
rbatis::插件::分页::{Page,PageRequest} |
√ |
rbs::Value |
√ |
serde_json::Value ...更多 serde 类型 |
√ |
rdbc-mysql::类型::* |
√ |
rdbc-pg::类型::* |
√ |
rdbc-sqlite::类型::* |
√ |
rdbc-mssql::类型::* |
√ |
支持的数据库驱动程序
如何为 RBatis 编写我的数据库驱动程序?
- 首先,定义你的驱动项目,添加 Cargo.toml 依赖项
[features]
default = ["tls-rustls"]
tls-rustls=["rbdc/tls-rustls"]
tls-native-tls=["rbdc/tls-native-tls"]
[dependencies]
rbs = { version = "4.5"}
rbdc = { version = "4.5", default-features = false, optional = true }
fastdate = { version = "0.3" }
tokio = { version = "1", features = ["full"] }
- 然后,你应该实现
rbdc::db::{ConnectOptions, Connection, ExecResult, MetaData, Placeholder, Row}
特性 - 完成。你的驱动程序已完成(你只需调用 RB.init() 方法)。它支持 RBatis 连接池/tls(本地,rustls)
#[tokio::main]
async fn main(){
let rb = rbatis::RBatis::new();
rb.init(YourDriver {}, "YourDriver://****").unwrap();
}
支持连接池
数据库(crates.io) | github_link |
---|---|
FastPool-默认 | rbatis/fast_pool |
Deadpool | Deadpool |
MobcPool | MobcPool |
支持的 Web 框架
- 任何 Web 框架,如 ntex, actix-web, axum, hyper, rocket, tide, warp, salvo 等。
快速示例:QueryWrapper 和常见用法(详细信息请参阅 example/crud_test.rs)
- Cargo.toml
默认
#rbatis deps
rbs = { version = "4.5"}
rbatis = { version = "4.5"}
rbdc-sqlite = { version = "4.5" }
#rbdc-mysql={version="4.5"}
#rbdc-pg={version="4.5"}
#rbdc-mssql={version="4.5"}
#other deps
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
log = "0.4"
fast_log = "1.6"
(可选) 'native-tls'
rbs = { version = "4.5" }
rbdc-sqlite = { version = "4.5", default-features = false, features = ["tls-native-tls"] }
#rbdc-mysql={version="4.5", default-features = false, features = ["tls-native-tls"]}
#rbdc-pg={version="4.5", default-features = false, features = ["tls-native-tls"]}
#rbdc-mssql={version="4.5", default-features = false, features = ["tls-native-tls"]}
rbatis = { version = "4.5"}
#other deps
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
log = "0.4"
fast_log = "1.6"
默认使用
//#[macro_use] define in 'root crate' or 'mod.rs' or 'main.rs'
#[macro_use]
extern crate rbatis;
extern crate rbdc;
use rbatis::rbdc::datetime::DateTime;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BizActivity {
pub id: Option<String>,
pub name: Option<String>,
pub pc_link: Option<String>,
pub h5_link: Option<String>,
pub pc_banner_img: Option<String>,
pub h5_banner_img: Option<String>,
pub sort: Option<String>,
pub status: Option<i32>,
pub remark: Option<String>,
pub create_time: Option<DateTime>,
pub version: Option<i64>,
pub delete_flag: Option<i32>,
}
crud!(BizActivity{});//crud = insert+select_by_column+update_by_column+delete_by_column
impl_select!(BizActivity{select_all_by_id(id:&str,name:&str) => "`where id = #{id} and name = #{name}`"});
impl_select!(BizActivity{select_by_id(id:String) -> Option => "`where id = #{id} limit 1`"});
impl_update!(BizActivity{update_by_name(name:&str) => "`where id = 1`"});
impl_delete!(BizActivity {delete_by_name(name:&str) => "`where name= '2'`"});
impl_select_page!(BizActivity{select_page(name:&str) => "`where name != #{name}`"});
#[tokio::main]
async fn main() {
/// enable log crate to show sql logs
fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
/// initialize rbatis. also you can call rb.clone(). this is an Arc point
let rb = RBatis::new();
/// connect to database
// sqlite
rb.init(SqliteDriver {}, "sqlite://target/sqlite.db").unwrap();
// mysql
// rb.init(MysqlDriver{},"mysql://root:123456@localhost:3306/test").unwrap();
// postgresql
// rb.init(PgDriver{},"postgres://postgres:123456@localhost:5432/postgres").unwrap();
// mssql/sqlserver
// rb.init(MssqlDriver{},"jdbc:sqlserver://localhost:1433;User=SA;Password={TestPass!123456};Database=test").unwrap();
let activity = BizActivity {
id: Some("2".into()),
name: Some("2".into()),
pc_link: Some("2".into()),
h5_link: Some("2".into()),
pc_banner_img: None,
h5_banner_img: None,
sort: None,
status: Some(2),
remark: Some("2".into()),
create_time: Some(DateTime::now()),
version: Some(1),
delete_flag: Some(1),
};
let data = BizActivity::insert(&rb, &activity).await;
println!("insert = {:?}", data);
let data = BizActivity::select_all_by_id(&rb, "1", "1").await;
println!("select_all_by_id = {:?}", data);
let data = BizActivity::select_by_id(&rb, "1".to_string()).await;
println!("select_by_id = {:?}", data);
let data = BizActivity::update_by_column(&rb, &activity, "id").await;
println!("update_by_column = {:?}", data);
let data = BizActivity::update_by_name(&rb, &activity, "test").await;
println!("update_by_name = {:?}", data);
let data = BizActivity::delete_by_column(&rb, "id", &"2".into()).await;
println!("delete_by_column = {:?}", data);
let data = BizActivity::delete_by_name(&rb, "2").await;
println!("delete_by_column = {:?}", data);
let data = BizActivity::select_page(&rb, &PageRequest::new(1, 10), "2").await;
println!("select_page = {:?}", data);
}
///...more usage,see crud.rs
- 原始 SQL
#[tokio::main]
pub async fn main() {
use rbatis::RBatis;
use rbdc_sqlite::driver::SqliteDriver;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct BizActivity {
pub id: Option<String>,
pub name: Option<String>,
}
fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
let rb = RBatis::new();
rb.init(SqliteDriver {}, "sqlite://target/sqlite.db").unwrap();
let table: Option<BizActivity> = rb
.query_decode("select * from biz_activity limit ?", vec![rbs::to_value!(1)])
.await
.unwrap();
let count: u64 = rb
.query_decode("select count(1) as count from biz_activity", vec![])
.await
.unwrap();
println!(">>>>> table={:?}", table);
println!(">>>>> count={}", count);
}
宏定义
- 重要更新(pysql 移除了运行时依赖,直接编译为静态 Rust 代码)这意味着使用 py_sql、html_sql 生成的 SQL 性能与手写代码大致相同。
由于编译时间的原因,需要声明所使用的数据库类型。
#[py_sql("select * from biz_activity where delete_flag = 0
if name != '':
`and name=#{name}`")]
async fn py_sql_tx(rb: &RBatis, tx_id: &String, name: &str) -> Vec<BizActivity> { impled!() }
- 添加了 html_sql 支持,一种类似于 MyBatis 的组织形式,以方便将 Java 系统迁移到 Rust(注意,构建时也编译为 Rust 代码,性能接近手写代码),这非常快。
由于编译时间的原因,需要声明所使用的数据库类型。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"https://raw.githubusercontent.com/rbatis/rbatis/master/rbatis-codegen/mybatis-3-mapper.dtd">
<mapper>
<select id="select_by_condition">
`select * from biz_activity where `
<if test="name != ''">
name like #{name}
</if>
</select>
</mapper>
///select page must have '?:&PageRequest' arg and return 'Page<?>'
#[html_sql("example/example.html")]
async fn select_by_condition(rb: &dyn Executor, page_req: &PageRequest, name: &str) -> Page<BizActivity> { impled!() }
use once_cell::sync::Lazy;
pub static RB: Lazy<RBatis> = Lazy::new(|| RBatis::new());
/// Macro generates execution logic based on method definition, similar to @select dynamic SQL of Java/Mybatis
/// RB is the name referenced locally by RBatis, for example DAO ::RB, com:: XXX ::RB... Can be
/// The second parameter is the standard driver SQL. Note that the corresponding database parameter mysql is? , pg is $1...
/// macro auto edit method to 'pub async fn select(name: &str) -> rbatis::core::Result<BizActivity> {}'
///
#[sql("select * from biz_activity where id = ?")]
pub async fn select(rb: &RBatis, name: &str) -> BizActivity {}
//or: pub async fn select(name: &str) -> rbatis::core::Result<BizActivity> {}
#[tokio::test]
pub async fn test_macro() {
fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
RB.link("mysql://root:123456@localhost:3306/test").await.unwrap();
let a = select(&RB, "1").await.unwrap();
println!("{:?}", a);
}
工作原理
依赖于 rbatis-codegen 在编译时从 html 文件创建对应结构的源代码(启用 debug_mode(Cargo.toml 中 rbatis = { features = ["debug_mode"]}
),可以观察生成的函数代码,并在运行时直接调用生成的函数。我们知道编译通常分为三个步骤:词法分析、语法分析和语义分析,以及中间代码生成。在 rbatis 中,词法分析由 rbatis-codegen
中的依赖 func.rs 处理,依赖于 syn 和 quote。解析由 rbatis-codegen
中的 parser_html 和 parser_pysql 完成。生成的语法树是 rbatis-codegen
中的 syntax_tree 包中定义的结构。中间代码生成由 func.rs 生成函数处理,所有支持的功能都定义在 rbatis-codegen
中。
上述描述发生在 cargo 构建阶段,这是 Rust 过程宏的编译阶段,其中 rbatis-codegen
生成的代码被交回 Rust 编译器进行 LLVM 编译,以生成纯机器代码。
因此,我认为 rbatis 是真正的零开销动态 SQL 编译时 ORM。
提交 PR(Pull Requests)
欢迎提交合并请求,并确保您添加的任何功能在测试包下都有适当的模拟单元测试函数。
变更日志
路线图
- 表同步插件,自动创建表/列(sqlite/mysql/mssql/postgres)
- 自定义连接池,连接池添加更多动态配置参数
- V5 版本
求助于 AI(AI帮助)
联系方式/捐赠,或 rbatis 点star
捐赠
联系方式(添加好友请备注'rbatis') 微信群:先加微信,然后拉进群
依赖
~11–22MB
~329K SLoC