SQLBoiler入门指导1-最好用的Go ORM框架

5,894 阅读5分钟

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

详细的官方文档翻译可以看SQLBoiler官方文档翻译--最好用的Go ORM框架

前言

最近调研了几个 github 上 star 数多的 ORM 框架,虽然功能都很齐全,但是在使用上却存在比较让人头疼的问题,如语法混乱语法冗余和最令人头疼的需要通过指针去获取返回值。在调研的过程中发现了一个神仙框架 SQLBoiler,它居然是直接返回查询结果的struct对象的,还可以选择是否返回 err,拥有完备好用无歧义的sql条件构造器,堪称 Go 语言版的 MybatisPlus。

但是 SQLBoiler 官方并没有提供中文文档,国内各个技术社区也没有相关的入门教学,故边学习官方英文文档边编写此入门指导。

这篇文章只会介绍 SQLBoiler 代码生成流程简单的增删查改操作,详细的功能在后续的文章中介绍。

代码生成

这里使用 MySQL 作为数据库,其他数据库配置和依赖可能会有所不同。

环境要求

  • Go 1.13以上
  • 表名列名使用蛇形命名法snake_case

数据库表结构

CREATE TABLE `user`  (
  `id` bigint(0) UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `nick_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '',
  `created_at` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
  `updated_at` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

第一步:下载安装代码生成插件

# Go 1.16 和之后:
go install github.com/volatiletech/sqlboiler/v4@latest
go install github.com/volatiletech/sqlboiler/v4/drivers/sqlboiler-mysql@latest

# Go 1.15 和之前:
GO111MODULE=on go get -u -t github.com/volatiletech/sqlboiler/v4
GO111MODULE=on go get github.com/volatiletech/sqlboiler/v4/drivers/sqlboiler-mysql

注:如果使用别的数据库需要把上面的mysql改成对应数据库的名字(如果支持的话)。

第二步:安装依赖

go get github.com/volatiletech/sqlboiler/v4
go get github.com/volatiletech/null/v8

第三步:配置文件

SQLBoiler 会自动识别根路径下的 sqlboiler.toml 配置文件。内容如下:

wipe     = true # 先删除之前自动生成的文件再重新生成
no-tests = true # 不生成测试代码
add-global-variants = true # 生成使用全局数据源的方法,也就是带 G 后缀的方法
add-panic-variants = true # 生成使用当 error 不为 nil 时 panic 的方法,也就是带 P 后缀的方法
no-context = true # 不需要上下文参数

[mysql]
dbname  = "dbname"
host    = "127.0.0.1"
user    = "root"
pass    = "root"
sslmode = "false"

注意:如果使用其他数据库可能配置会有区别,详细的配置项可以看官方文档

第四步:生成代码

在命令行输入下面命令即可生成图片models目录中的代码:

sqlboiler mysql 

image.png

增删查改例子

设置数据源

import (
   "database/sql"
   "fmt"
   _ "github.com/go-sql-driver/mysql"
   "github.com/volatiletech/sqlboiler/v4/boil"
   "github.com/volatiletech/sqlboiler/v4/queries/qm"
   "log"
   "sqlboiler_test/models"
)

func main() {
   // 创建数据源
   db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/tbname?parseTime=True") 
   if err != nil {
      log.Fatal(err)
   }
   // 设置全局数据源
   boil.SetDB(db) 
}

Insert

func InsertUserDemo() {
   user := models.User{
      Username: "username1",
      Password: "123456",
      NickName: "nickName1",
   }
   user.InsertGP(boil.Infer())
   fmt.Printf("%+v", user)
}

// 输出
{ID:7 Username:username1 Password:123456 NickName:nickName1 CreatedAt:2021-07-18 13:34:16.3409056 +0000 UTC UpdatedAt:2021-07-18 13:34:16.3409056 +0000 UTC R:<nil> L:{}}

可以看到插入操作只需一行代码 user.InsertGP(boil.Infer()) 即可,插入后会自动设置 IDCreateAtUpdatedAt 等自动生成的值。其中 boil.Infer() 是智能选择插入字段,Go 零值字段不会被选中,但是插入后会根据数据库生成的值设置这些字段(如自增主键,默认账,更新时间,创建时间等)。

InsertGP()GP 表示使用全局数据源(G)和不返回 error(P,当返回 error 不为 nil 时直接 panic),RL 字段是 SQLBoiler 插件自动生成现在可以先忽略。

Delete

func DeleteUserDemo() {
   // 根据 User.ID 进行删除
   user := models.User{ID: 1}
   count := user.DeleteGP()
   fmt.Println(count)

   // 根据 UserSlice 批量删除
   users := models.Users().AllGP()
   count = users.DeleteAllGP()
   fmt.Println(count)

   // 根据条件进行删除
   count, _ = models.Users(models.UserWhere.Username.EQ("username1")).DeleteAllG()
   fmt.Println(count)
}

删除操作也需要一行,可以根据 User struct 进行删除,根据 UserSlice 批量删除,也可以根据条件进行删除,返回值是成功删除的行数。

Select

func SelectUserDemo() {
   // 根据条件查询(如果不指定条件则查询全部)
   users := models.Users(qm.Select(models.UserColumns.ID, models.UserColumns.NickName),
      models.UserWhere.Username.EQ("username1")).AllGP()
   fmt.Println(users)

   // 查询数量
   count := models.Users(models.UserWhere.Username.EQ("username1")).CountGP()
   fmt.Println(count)

   // 根据主键查询
   user, err := models.FindUserG(1)
   if err == sql.ErrNoRows {
      // 业务处理
   }
   fmt.Println(user)
}

可以根据条件查询记录,查询数量,还可以根据主键查询。注意 FinUserG() 在查询不到记录的时候会返回 sql.ErrNoRows,所以最好判断一下。

qm.Select() 可以用来指定查询的列。

Update

func UpdateUserDemo() {
   // 根据 User.ID 进行更新
   user := models.User{ID: 1, NickName: "nickName1001"}
   count := user.UpdateGP(boil.Whitelist(models.UserColumns.NickName))
   fmt.Println(count)

   // 根据 UserSlice 批量更新
   users := models.Users().AllGP()
   count = users.UpdateAllGP(models.M{models.UserColumns.NickName: "updateAllNickName"})
   fmt.Println(count)

   // 根据条件进行更新
   count, _ = models.Users(models.UserWhere.NickName.EQ("updateAllNickName")).
      UpdateAllG(models.M{models.UserColumns.NickName: "updateAllNickName2"})
   fmt.Println(count)
}

与 Delete 操作类似,可以根据 User struct 进行更新,也可以根据 UserSlice 批量更新,还可以根据条件进行更新。

这里需要注意,在根据 User struct 进行更新时,最好使用 boil.Whitelist() 指定更新的字段,因为如果使用 boil.Infer() 就算是零值也会进行更新。

models.M{} 表示更新的字段和对应的值。

完整代码

package main

import (
   "database/sql"
   "fmt"
   _ "github.com/go-sql-driver/mysql"
   "github.com/volatiletech/sqlboiler/v4/boil"
   "github.com/volatiletech/sqlboiler/v4/queries/qm"
   "log"
   "sqlboiler_test/models"
)

func main() {
   // 创建数据源
   db, err := sql.Open("mysql",
      "root:root@tcp(127.0.0.1:3306)/tbname?parseTime=True")
   if err != nil {
      log.Fatal(err)
   }
   // 设置全局数据源
   boil.SetDB(db)

   //InsertUserDemo()
   //DeleteUserDemo()
   //SelectUserDemo()
   //UpdateUserDemo()
}

func InsertUserDemo() {
   user := models.User{
      Username: "username1",
      Password: "123456",
      NickName: "nickName1",
   }
   user.InsertGP(boil.Infer())
   fmt.Printf("%+v", user)
}

func DeleteUserDemo() {
   // 根据 User.ID 进行删除
   user := models.User{ID: 1}
   count := user.DeleteGP()
   fmt.Println(count)

   // 根据 UserSlice 批量删除
   users := models.Users().AllGP()
   count = users.DeleteAllGP()
   fmt.Println(count)

   // 根据条件进行删除
   count, _ = models.Users(models.UserWhere.Username.EQ("username1")).DeleteAllG()
   fmt.Println(count)
}

func SelectUserDemo() {
   // 根据条件查询(如果不指定条件则查询全部)
   users := models.Users(qm.Select(models.UserColumns.ID, models.UserColumns.NickName),
      models.UserWhere.Username.EQ("username1")).AllGP()
   fmt.Println(users[0])

   // 查询数量
   count := models.Users(models.UserWhere.Username.EQ("username1")).CountGP()
   fmt.Println(count)

   // 根据主键查询
   user, err := models.FindUserG(1)
   if err == sql.ErrNoRows {
      // 业务处理
   }
   fmt.Println(user)
}

func UpdateUserDemo() {
   // 根据 User.ID 进行更新
   user := models.User{ID: 1, NickName: "nickName1001"}
   count := user.UpdateGP(boil.Whitelist(models.UserColumns.NickName))
   fmt.Println(count)

   // 根据 UserSlice 批量更新
   users := models.Users().AllGP()
   count = users.UpdateAllGP(models.M{models.UserColumns.NickName: "updateAllNickName"})
   fmt.Println(count)

   // 根据条件进行更新
   count, _ = models.Users(models.UserWhere.NickName.EQ("updateAllNickName")).
      UpdateAllG(models.M{models.UserColumns.NickName: "updateAllNickName2"})
   fmt.Println(count)
}

详细的官方文档翻译可以看SQLBoiler官方文档翻译--最好用的Go ORM框架