Gopher 苦 ORM 久矣,发布个使用 rawsql 节省体力的包,使用姿势与标准库基本相同

2022-06-23 19:13:34 +08:00
 lesismal

初衷

这是前几天看到隔壁帖子吵得热闹,一时兴起写的,还没大规模测试,感兴趣的同学欢迎来 issue/pr 骚扰。

隔壁帖子: /t/859178

项目地址

示例代码

特点:

  1. 非 ORM ,仍然是标准库的姿势使用 rawsql
  2. 简单封装了标准库 sql ,Query 相关的方法增加了一个参数来接收结果,不再需要自己处理 rows
  3. 简洁干净,目前只使用标准库,无三方依赖

直接上代码吧:

db, err := sqlw.Open("mysql", "test:123qwe@tcp(localhost:3306)/sqlw_test", "db")
if err != nil {
      log.Fatal(err)
}

var dst examples.ModelForTest
err = db.QueryRow(&dst, `select * from sqlw_test.sqlw_test`)
if err != nil {
      log.Fatal(err)
}
log.Printf("dst: %v", dst)

var dsts []*examples.ModelForTest
err = db.QueryRow(&dsts, `select * from sqlw_test.sqlw_test`)
if err != nil {
      log.Fatal(err)
}
log.Printf("dst: %v", dsts )

db.Prepare|sql.Stmt, db.Begin|Tx 也类似,保持了标准库的简洁基础上省去了自己遍历 rows 去 scan 的麻烦,代码量节省很多

3440 次点击
所在节点    Go 编程语言
33 条回复
jam1024
2022-06-25 10:09:39 +08:00
@lesismal 就像高级语言你不用,非要去用 C 语言,追求所谓的更好颗粒的控制,最后却本事不够,反倒遇到各种坑,写出的东西问题多多
lesismal
2022-06-25 10:36:41 +08:00
@jam1024
如果按这个逻辑,那 c 语言早该被消灭了才对。然而并不是所有业务都是简单的 curd 。
另一个话题,curder 的收入也是个很难突破的瓶颈,要提升实力和收入,怎么办?固守所谓的使用高级语言?当顶流量级项目来临需要应对性能问题自己无从下手只能看着别人出手?
不能要求并期望所有人都只做 curder 的。
lesismal
2022-06-25 10:44:01 +08:00
@liaohongxing go 事务的用法是 tx, err := db.Begin(),但创建 tx 后的执行多条语句也是由用户实现的、框架层不好去判断,正确的用法是创建 tx 成功后直接 defer tx.Rollback() 就可以了,后面如果 Commit 成功了、defer Rollback 执行时会失败但不影响正确的逻辑,defer tx.Rollback()还可以避免连接泄露,比如中途 panic 了、既没有 Commit 也没有 Rollback ,因为 tx 独占一个 sql 连接,只有 Commit 或 Rollback 后才会把连接放回连接池给其他地方用。正确用法参考:
github.com/lesismal/sqlw_examples/blob/main/tx/tx.go#L26
直接使用标准库同样也是应该这样子的,否则就存在泄露的隐患
lesismal
2022-06-25 10:51:35 +08:00
@EscYezi #19
首先,这写法似乎并没有比 sqlw 现在的样子更省事呀,兄弟你看我的示例代码。
而且 go 的泛型不是万能的,泛型适合具有同一类别方法的类型比如数值类,都可以进行基础的数学运算,然后泛型里不同的类型实例也可以加减乘除,但是如果你的逻辑里包括用泛型的数学运算但是你实际传的确实一个结构体进来,就不行了。c++那种泛型方便是因为可以操作符重载可以把各种需要的 class/struct 实现同类方法( go 的接口能实现这种但是不能满足泛型的需求比如返回值的类型),而 go 没有操作符重载。对于未知类型类别的参数,sql 库里去做字段映射时,仍然是需要用 reflect 实现。
lesismal
2022-06-25 10:55:02 +08:00
@liaohongxing 也可以做自动 Rollback ,sqlw.Tx 再把 sql.Tx 的 Exec 包装一道,然后 Query/Exec 里都先判断下 sql.Tx 执行是否有 err ,有就回滚。但是这样的话,与标准库用法又有差异了,用户外面如果也写了 if err rollback 倒是影响不大。
我先琢磨琢磨看要不要加自动回滚
lesismal
2022-06-26 10:14:12 +08:00
@liaohongxing 想了下,还是不应该自动 Rollback ,比如一个事务多个语句,其中一个语句 duplicate key 了,但是业务层自己会判断 duplicate key 并继续执行事务其他逻辑,如果框架自动判断 err 回滚了,业务层就没法继续了
yiplee
2022-06-27 09:24:31 +08:00
Go Rawsql 我更推荐 github.com/kyleconroy/sqlc 这种实现,直接根据 sql 生成 scan 的代码。
lesismal
2022-06-27 10:19:48 +08:00
@yiplee
sqlc 用着还是有点麻烦的,我昨天更了一版直接增删改查也都提供了,应该是比 sqlc/sqlx 省事多了,不信你看下。。。
github.com/lesismal/sqlw
shujun
2022-06-27 11:22:06 +08:00
要的就是这种,其实 gorm 我也是这样用的,rawsql 清晰明了,省的每次要去翻 ORM 的文档。
yiplee
2022-06-27 11:32:07 +08:00
@lesismal #28 没看出来 Delete 和 Update 这些方法存在的作用,不都是 Exec 么
lesismal
2022-06-27 11:55:20 +08:00
@yiplee
使用 Query/Exec 可以实现所有操作,毕竟标准库就是这样的。

Select/Delete 都只是 Query/Exec 的简单封装,主要是为了用户层使用时的语义更加明了,Insert/Update 同样也可以更明了一些。

单独提供 Update 的另一个原因,是由于直接使用 Exec 不知道是增删改查哪种操作,所以不方便处理结构体与 sql 字段的映射,比如 Exec 的方式我用当前的一个 Model Obj 字段去 Update ,还是得像标准库一样把 Obj 每个字段都列出来,而现在可以只传一个 Obj 。如果是想把 Exec 也支持结构体映射,则需要再去做一层 sql 语句类型判断,一是又多浪费了一点性能二是复杂语句判断逻辑实现起来也吃力,比如 select for update 、多条语句全都有之类的,所以单独提供了 Update ,复杂的需求才去用 Exec 。

总结下来就是 业务层更简洁明了+降低实现复杂度。
lesismal
2022-06-27 12:04:21 +08:00
@yiplee 其实最开始只是想把 Query 做下映射的、免去了 for rows.Next scan 的麻烦,但是自己写点示例的时候使用 Exec 还是不够方便,所以这两天又加了其他三个
lesismal
2022-06-27 12:09:58 +08:00
@lesismal #31

"Select/Delete 都只是 Query/Exec 的简单封装"
——这里的 Select 简单封装是只 sqlw.Select 是对 sqlw.Query 的封装,不是指对标准库的简单封装

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/861739

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX