Rust sqlx SQLBuilder
对于个人来说,一直不喜欢ORM
框架。明明就是熟悉sql
的,为何还要封装一层?抛开针对各个数据库厂商难以作sql
优化之外,封装之后的写法也不见得优雅多少,而且每个ORM
框架写法有差异,还要针对各个ORM
框架学习一种新的语法和规则,不是蛋疼么?不过,裸操sql
也难免太过暴力,不断的重写模板代码,也是挺操蛋的。所以类似于MyBatis
之类的半自动框架,或者说这类sql builder
挺收欢迎的。对于rust
生态来说,sqlx是比较收欢迎的框架,很多ORM
也是基于它来实现的。
sqlx
是纯rust
编写的,异步的可选不同的runtime
,而它最大的特点就编译期间可以进行sql
检查,如果下面,字段名写错了,直接就被rls
,检测出来。
SQLx CLI
sqlx
自带一个client,sqlx-cli
用于简单的数据库管理和迁移。具体的使用方式参考自带的文档:https://github.com/launchbadge/sqlx/tree/main/sqlx-cli
不过如果,使用该方式会在数据库产生额外的表,该表用于迁移管理:
|
|
按照文档说明:sqlx migrate add <name>
添加-r
选项可以在生成的文件后面增加down/up
后缀名,用于revert
回退,实际上的操作并不会,可以手工添加。不过sqlx migrate run
是按时间顺直接执行的,直接新建一个,也是可以实现类似回退的效果。
这种开发的方式是否适合国内的情况呢?感觉这个还是有待商榷的。不一定是所有的模块都是用rust编写,让人难以接受的时候增加一张_sqlx_migrations
表,对数据库强入侵。另外一个就是migrations
文件可以记录到数据库设计的变迁历史,可是国内的大多数情况之下,运维那一侧更加希望的是由统一的一个sql
,让他们执行,而不是由程序一个migrate run
。这样对数库权限控制基本就是忽视的了。
因此是否使用这个sqlx-cli
就要根据实际情况觉定。
Query(CURD)
查询的有两种,一种是unprepared
,另外一种是prepared
的。两种使用方式:
// low-level, Executor trait conn.execute("BEGIN").await?; // unprepared, simple query conn.execute(sqlx::query("DELETE FROM table")).await?; // prepared, cached query
两者之间的区别在在于一个是裸字符串,另外一个是sqlx
函数包裹。我们大多情况之下都会使用prepared
的,因为性能较高。
对于prepared
的查询,也有两种使用方式,一种是使用宏,另外一种是使用api。如:
|
|
两者是有明显的区别。使用宏的方式,是需要配置.env
指定数据库的url,在编译期间实时运行。不过使用宏,也有一个问题:如果你使用的是Any
数据库驱动,想根据连接字符串来区别不同的数据库厂商,那么宏的方式就会直接报错。这个问题在2021年就提出了:https://github.com/launchbadge/sqlx/issues/964,并且也有了解决的方案,可是现在两年多过去了,一直没有按照解决的方案实现,可能这个需求比较小众。
使用api的方式也是有一个问题,英文不同的数据库厂商返回的数据是不一定一样的,比如有些数据库,他是没有返回last_insert_id
的。所以,如果是要写此类代码,还是需要注意差异。
将查询结果转换为struct
基本就实现了半自动框架功能了,操作的方式就是实现sqlx::FromRow
这个trait
,以及用宏的方式提供了,如:
|
|
事务操作方式也是挺简单:
|
|
注意.execute(&mut *tx)
的调用,Transaction
对象实现了deref
,所以可以传递给execute
。
小结
以MyBatis
作为对比,sqlx
基本具备MyBatis
的基本功能。在此基础上并且还提供了额外的,如编译期间检测,sqlx-cli
数据库管理等功能。虽然这些额外功能在正常的开发之中不一定能用的上,不过这也不失为他作为一个强大的sql builder
。