A highly performant ORM framework written in Rust, inspired by Mybatis and MybatisPlus.
Dynamic SQL, no runtime, no Garbage Collector, and low memory use.
Supports async_std, tokio
This crate uses #![forbid(unsafe_code)] to ensure everything is implemented in 100% safe Rust.
30% faster than golang's mybatis!
Why not diesel or not sqlx ?
Framework |
Async/.await |
Learning curve |
Supports for xml/Wrapper/built-in CRUD |
Logical delete plugin |
Pagination plugin |
rbatis |
√ |
easy |
√ |
√ |
√ |
sqlx |
√ |
hard (depends on macros and env. variables) |
x |
x |
x |
diesel |
x |
hard (use FFI, unsafe) |
x |
x |
x |
Performance comparison with Golang (in a docker environment)
Framework |
Mysql(docker) |
SQL statement(10k) |
ns/operation(lower is better) |
Qps(higher is better) |
Memory usage(lower is better) |
Rust-rbatis/tokio |
1 CPU, 1G memory |
select count(1) from table; |
965649 ns/op |
1035 Qps/s |
2.1MB |
Go-GoMybatis/http |
1 CPU, 1G memory |
select count(1) from table; |
1184503 ns/op |
844 Qps/s |
28.4MB |
- If necessary, you can use the same XML file data as Mybatis,Easy project migration from java to Rust
- used json with serde_json for passing parameters and communication
- high performance, single threaded benchmark can easily achieve 200,000 QPS - data returned from database directly (zero lookup time) on a Windows 10 6 core i7 with 16 GB memory machine. Performace will be better using multiple threads, and it outperforms Go's GoMyBatis.
- supports logical deletes, pagination, py-like SQL and basic Mybatis functionalities.
- supports future,(in theory, if all io operations are replaced with async_std/tokio, it could achieve higher concurrency than Go-lang)
- supports logging, customizable logging based on
log
crate
- used 100% safe Rust with
#![forbid(unsafe_code)]
enabled
- rbatis/example (import into Clion!)
- website back end example(import into Clion!)
Example Cargo.toml
# add this library,and cargo install
# json (required)
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# Date time (required)
chrono = { version = "0.4", features = ["serde"] }
# logging (required)
log = "0.4"
fast_log="1.3"
# BigDecimal (optional)
bigdecimal = "0.2"
# rbatis, must maintain the same versions (required)
rbatis-core = { version = "1.8.8", features = ["all"]}
rbatis = { version = "1.8.8" }
rbatis-macro-driver = { version = "1.8.8" }
Quick example: QueryWrapper and common usages (see example/crud_test.rs for details)
#[macro_use]
extern crate rbatis_macro_driver;
#[derive(CRUDEnable,Serialize, Deserialize, Clone, Debug)]
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<NaiveDateTime>,
pub version: Option<i32>,
pub delete_flag: Option<i32>,
}
#[actix_rt::main]
async fn main() {
let rb = Rbatis::new();
rb.link("mysql://root:123456@localhost:3306/test").await.unwrap();
let wrapper = rb.new_wrapper()
.eq("id", 1) .and() .ne("id", 1) .in_array("id", &[1, 2, 3]) .not_in("id", &[1, 2, 3]) .like("name", 1) .or() .not_like("name", "asdf") .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();
let activity = BizActivity {
id: Some("12312".to_string()),
name: None,
remark: None,
create_time: Some(NaiveDateTime::now()),
version: Some(1),
delete_flag: Some(1),
};
rb.save("",&activity).await;
rb.save_batch("", &vec![activity]).await;
let result: Option<BizActivity> = rb.fetch_by_id("", &"1".to_string()).await.unwrap();
let result: Vec<BizActivity> = rb.list("").await.unwrap();
let result: Vec<BizActivity> = rb.list_by_ids("",&["1".to_string()]).await.unwrap();
let w = rb.new_wrapper().eq("id", "1").check().unwrap();
let r: Result<Option<BizActivity>, Error> = rb.fetch_by_wrapper("", &w).await;
rb.remove_by_id::<BizActivity>("", &"1".to_string()).await;
rb.remove_batch_by_id::<BizActivity>("", &["1".to_string(), "2".to_string()]).await;
let w = rb.new_wrapper().eq("id", "12312").check().unwrap();
rb.update_by_wrapper("", &activity, &w).await;
}
macros (new addition)
lazy_static! {
static ref RB:Rbatis=Rbatis::new();
}
#[sql(RB, "select * from biz_activity where id = ?")]
fn select(name: &str) -> BizActivity {}
#[async_std::test]
pub async fn test_macro() {
fast_log::init_log("requests.log",
1000,
log::Level::Info,
Some(Box::new(ModuleFilter::new_exclude(vec!["sqlx".to_string()]))),
true);
RB.link("mysql://root:123456@localhost:3306/test").await.unwrap();
let a = select("1").await.unwrap();
println!("{:?}", a);
}
lazy_static! {
static ref RB:Rbatis=Rbatis::new();
}
#[py_sql(RB, "select * from biz_activity where id = #{name}
if name != '':
and name=#{name}")]
fn py_select(name: &str) -> Option<BizActivity> {}
#[async_std::test]
pub async fn test_macro_py_select() {
fast_log::init_log("requests.log",
1000,
log::Level::Info,
Some(Box::new(ModuleFilter::new_exclude(vec!["sqlx".to_string()]))),
true);
RB.link("mysql://root:123456@localhost:3306/test").await.unwrap();
let a = py_select("1").await.unwrap();
println!("{:?}", a);
}
How to use logical deletes plugin (works for fetching or removing functions provided by rbatis,e.g. list**(),remove**(),fetch**())
let mut rb = init_rbatis().await;
rb.logic_plugin = Some(Box::new(RbatisLogicDeletePlugin::new("delete_flag")));
rb.link("mysql://root:123456@localhost:3306/test").await.unwrap();
let r = rb.remove_batch_by_id::<BizActivity>("", &["1".to_string(), "2".to_string()]).await;
if r.is_err() {
println!("{}", r.err().unwrap().to_string());
}
How to use pagination plugin
let mut rb = Rbatis::new();
rb.link("mysql://root:123456@localhost:3306/test").await.unwrap();
let req = PageRequest::new(1, 20);
let wraper= rb.new_wrapper()
.eq("delete_flag",1)
.check()
.unwrap();
let data: Page<BizActivity> = rb.fetch_page_by_wrapper("", &wraper, &req).await.unwrap();
println!("{}", serde_json::to_string(&data).unwrap());
{
"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-09T00:00:00+00:00",
"version": 1,
"delete_flag": 1
}],
"total": 5,
"size": 20,
"current": 1,
"serch_count": true
}
py-like sql example
//Execute to remote mysql and get the result. Supports any serializable type of SERde_JSON
let rb = Rbatis::new();
rb.link("mysql://root:123456@localhost:3306/test").await.unwrap();
let py = r SELECT * FROM biz_activity
WHERE delete_flag = if name != null:
AND name like if ids != null:
AND id in (
trim ',':
for item in ids:
)"#;
let data: serde_json::Value = rb.py_fetch("", py, &json!({ "delete_flag": 1 })).await.unwrap();
println!("{}", data);
logging system with fast_log here as an example
use log::{error, info, warn};
fn main(){
fast_log::init_log("requests.log",
1000,
log::Level::Info,
Some(Box::new(ModuleFilter::new_exclude(vec!["sqlx".to_string()]))),
true);
info!("print data");
}
Customize connection pool's size, timeout, active number of connections, and etc.
use rbatis_core::db::PoolOptions;
pub async fn init_rbatis() -> Rbatis {
let rb = Rbatis::new();
let mut opt = PoolOptions::new();
opt.max_size = 20;
rb.link_opt("mysql://root:123456@localhost:3306/test", &opt).await.unwrap();
}
How to use XML
/**
* table
*/
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Activity {
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<NaiveDateTime>,
pub version: Option<i32>,
pub delete_flag: Option<i32>,
}
fn main() {
async_std::task::block_on(
async move {
fast_log::init_log("requests.log",
1000,
log::Level::Info,
Some(Box::new(ModuleFilter::new_exclude(vec!["sqlx".to_string()]))),
true);
let mut rb = Rbatis::new();
rb.link("mysql://root:123456@localhost:3306/test").await.unwrap();
//xml数据建议以 XXMapper.xml 的格式存储管理
rb.load_xml("test", r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://raw.githubusercontent.com/zhuxiujia/Rbatis/master/rbatis-mapper.dtd">
<mapper>
<result_map id="BaseResultMap" table="biz_activity">
<id column="id"/>
<result column="name" lang_type="string"/>
<result column="pc_link" lang_type="string"/>
<result column="h5_link" lang_type="string"/>
<result column="pc_banner_img" lang_type="string"/>
<result column="h5_banner_img" lang_type="string"/>
<result column="sort" lang_type="string"/>
<result column="status" lang_type="number"/>
<result column="remark" lang_type="string"/>
<result column="version" lang_type="number" version_enable="true"/>
<result column="create_time" lang_type="time"/>
<result column="delete_flag" lang_type="number" logic_enable="true" logic_undelete="1"
logic_deleted="0"/>
</result_map>
<select id="select_by_condition">
<bind name="pattern" value="'%' + name + '%'"/>
select * from biz_activity
<where>
<if test="name != null">and name like #{pattern}</if>
<if test="startTime != null">and create_time >= #{startTime}</if>
<if test="endTime != null">and create_time <= #{endTime}</if>
</where>
order by create_time desc
<if test="page != null and size != null">limit #{page}, #{size}</if>
</select>
</mapper>"#).unwrap();
let arg = &json!({
"delete_flag": 1,
"name": "test",
"startTime": null,
"endTime": null,
"page": 0,
"size": 20
});
let data: Vec<BizActivity> = rb.xml_fetch("", "test", "select_by_condition", arg).await.unwrap();
println!("{}", serde_json::to_string(&data).unwrap_or("".to_string()));
}
)
}
//输出结果
//2020-06-27T03:13:40.422307200+08:00 INFO rbatis::rbatis - [rbatis] >> fetch sql: select * from biz_activity where name like ? order by create_time desc limit ? , ? (src\rbatis.rs:198)
//2020-06-27T03:13:40.424307300+08:00 INFO rbatis::rbatis - [rbatis] >> fetch arg:["%test%",0,20] (src\rbatis.rs:199)
//2020-06-27T03:13:40.446308900+08:00 INFO rbatis::rbatis - [rbatis] << 4 (src\rbatis.rs:234)
//[{"id":"221","name":"test","pc_link":"","h5_link":"","pc_banner_img":null,"h5_banner_img":null,"sort":"0","status":0,"remark":"","create_time":"2020-06-17T20:10:23Z","version":0,"delete_flag":1},{"id":"222","name":"test","pc_link":"","h5_link":"","pc_banner_img":null,"h5_banner_img":null,"sort":"0","status":0,"remark":"","create_time":"2020-06-17T20:10:23Z","version":0,"delete_flag":1},{"id":"223","name":"test","pc_link":"","h5_link":"","pc_banner_img":null,"h5_banner_img":null,"sort":"0","status":0,"remark":"","create_time":"2020-06-17T20:10:23Z","version":0,"delete_flag":1},{"id":"178","name":"test_insret","pc_link":"","h5_link":"","pc_banner_img":null,"h5_banner_img":null,"sort":"1","status":1,"remark":"","create_time":"2020-06-17T20:08:13Z","version":0,"delete_flag":1}]
Async/.await
task support
async_std::task::block_on(async {
let rb = Rbatis::new();
rb.link("mysql://root:123456@localhost:3306/test").await.unwrap();
let tx_id = "1"; rb.begin(tx_id).await.unwrap();
let v: serde_json::Value = rb.fetch(tx_id, "SELECT count(1) FROM biz_activity;").await.unwrap();
println!("{}", v.clone());
rb.commit(tx_id).await.unwrap();
});
How to use rbatis with Rust web frameworks (actix-web is used here as an example, but all web frameworks based on tokio or async_std are supported)
lazy_static! {
static ref RB:Rbatis=Rbatis::new();
}
async fn index() -> impl Responder {
let v:Result<i32,rbatis_core::Error> = RB.fetch("", "SELECT count(1) FROM biz_activity;").await;
HttpResponse::Ok().body(format!("count(1)={}",v.unwrap_or(0)))
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
fast_log::init_log("requests.log",
1000,
log::Level::Info,
Some(Box::new(ModuleFilter::new_exclude(vec!["sqlx".to_string()]))),
true);
RB.link("mysql://root:123456@localhost:3306/test").await.unwrap();
HttpServer::new(|| {
App::new()
.route("/", web::get().to(index))
})
.bind("127.0.0.1:8000")?
.run()
.await
}
Supported data structures
data structure |
is supported |
Option |
√ |
Vec |
√ |
HashMap |
√ |
Slice |
√ |
i32,i64,f32,f64,bool,String...more rust type |
√ |
NativeDateTime |
√ |
BigDecimal |
√ |
serde_json::Value...more serde type |
√ |
Supported database √supported .WIP
database |
is supported |
Mysql |
√ |
Postgres |
√ |
Sqlite |
√ |
Mssql/Sqlserver |
√ |
MariaDB(Mysql) |
√ |
TiDB(Mysql) |
√ |
CockroachDB(Postgres) |
√ |
Supported OS/Platforms
platform |
is supported |
Linux |
√ |
Apple/MacOS |
√ |
Windows |
√ |
Progress - in sequential order
function |
is supported |
CRUD, with built-in CRUD template (built-in CRUD supports logical deletes) |
√ |
LogSystem (logging component) |
√ |
Tx(task/Nested transactions) |
√ |
Py(using py-like xml-equivalent statement in SQL) |
√ |
async/await support |
√ |
PagePlugin(Pagincation) |
√ |
LogicDelPlugin |
√ |
DataBaseConvertPlugin(The database table structure is converted to the configuration plug-in) |
x |
web(Web UI) |
x |
- Conlusion: Assuming zero time consumed on IO, single threaded benchmark achieves 200K QPS or QPS, which is a few times more performant than GC languages like Go or Java.
FAQ
- Support for DateTime and BigDecimal?
Currently supports chrono::NaiveDateTime和bigdecimal::BigDecimal
- Supports for
async/.await
Currently supports both async_std
and tokio
- Stmt in postgres uses $1, $2 instead of ? in Mysql, does this require some special treatment?
No, because rbatis uses #{} to describe parametric variabls, you only need to write the correct parameter names and do not need to match it with the symbols used by the database.
- Supports for Oracle database driver?
No, moving away from IOE is recommended.
- Which crate should be depended on if only the driver is needed?
rbatis-core, Cargo.toml add rbatis-core = "*"
- How to select
async/.await
runtime?
# add the line below in Cargo.toml to used async_std
async-std = { version = "*", features = ["attributes","tokio02"] }
rbatis-core = { version = "*", default-features = false , features = ["all","tokio02"] }
rbatis = { version = "*", default-features = false , features = ["tokio02"] }
changelog
changelog
Related Projects
In order to achieve the satisfaction of this ORM framework, your support is always our motivation, we are eager to welcome WeChat to donate to support us ~ or ~ star at the top right corner
为了称心如意的ORM框架,您的支持永远是我们的动力,迫切欢迎微信捐赠支持我们 或者右上角点下star