Gendry 辅助操作数据库的 Go 包

发布于 07-01 21:46 字数 5409 浏览 1674 评论 0

Gendry 是一个用于辅助操作数据库的 Go 包。基于 go-sql-driver/mysql,它提供了一系列的方法来为你调用标准库 database/sql 中的方法准备参数。

Gendery 主要分为 3 个独立的部分,你可以单独使用任何一个部分:

  • manager
  • builder
  • scanner
  • CLI Tool

Manager

Manager 主要用来初始化连接池(也就是 sql.DB 对象),设置各种参数,因此叫 manager。你可以设置任何 go-sql-driver/mysql 驱动支持的参数。 初始化连接池时,代码如下:

var db *sql.DB
var err error
db, err = manager
  .New(dbName, user, password, host)
  .Set(
    manager.SetCharset("utf8"),
    manager.SetAllowCleartextPasswords(true),
    manager.SetInterpolateParams(true),
    manager.SetTimeout(1 * time.Second),
    manager.SetReadTimeout(1 * time.Second)
  ).Port(3302).Open(true)

事实上,manager 做的事情就是就是生成 dataSouceName

dataSourceName 的一般格式为:

[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]

manager 是基于数据库驱动 go-mysql-driver/mysql 而开发的,manager 支持了几乎所有该驱动支持的参数设置。具体用法看 manager 的 README。

Builder

builder 顾名思义,就是构建生成sql语句。手写 sql 虽然直观简单,但是可维护性差,最主要的是硬编码容易出错。而且如果遇到大 where in 查询,而 in 的集合内容又是动态的,这就非常麻烦了。

builder 不是一个 ORM(我们开发 Gendry 的重要原因之一就是不喜欢 ORM),它只是提供简单的 API 帮你生成 sql 语句,如:

where := map[string]interface{}{
  "city": []string{"beijing", "shanghai"},
  // 默认可以省略 in 操作符,等同于:
  // "city in": []string{"beijing", "shanghai"},
  "score": 5,
  "age >": 35,
  "address": builder.IsNotNull,
  "_orderby": "bonus desc",
  "_groupby": "department",
}
table := "some_table"
selectFields := []string{"name", "age", "sex"}
cond, values, err := builder.BuildSelect(table, where, selectFields)

//cond = SELECT name,age,sex FROM g_xxx WHERE (score=? AND city IN (?,?) AND age>? AND address IS NOT NULL) GROUP BY department ORDER BY bonus DESC
//values = []interface{}{"beijing", "shanghai", 5, 35}

rows,err := db.Query(cond, values...)

默认 where 参数中可以根据 value(reflect.Slice)类型来自动的添加 in 参数

where := map[string]interface{}{
  "city": []string{"beijing", "shanghai"},
}

如果你想清除 where map 中的零值可以使用 builder.OmitEmpty

where := map[string]interface{}{
  "score": 0,
  "age": 35,
  }
finalWhere := builder.OmitEmpty(where, []string{"score", "age"})
// finalWhere = map[string]interface{}{"age": 35}

// support: Bool, Array, String, Float32, Float64, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr, Map, Slice, Interface, Struct

同时,builder 还提供一个便捷方法来进行聚合查询,比如:count,sum,max,min,avg

where := map[string]interface{}{
    "score > ": 100,
    "city": []interface{}{"Beijing", "Shijiazhuang",}
}
// AggregateSum,AggregateMax,AggregateMin,AggregateCount,AggregateAvg is supported
result, err := AggregateQuery(ctx, db, "tableName", where, AggregateSum("age"))
sumAge := result.Int64()

result,err = AggregateQuery(ctx, db, "tableName", where, AggregateCount("*")) 
numberOfRecords := result.Int64()

result,err = AggregateQuery(ctx, db, "tableName", where, AggregateAvg("score"))
averageScore := result.Float64()

对于比较复杂的查询, NamedQuery 将会派上用场:

cond, vals, err := builder.NamedQuery("select * from tb where name={{name}} and id in (select uid from anothertable where score in {{m_score}})", map[string]interface{}{
  "name": "caibirdme",
  "m_score": []float64{3.0, 5.8, 7.9},
})

assert.Equal("select * from tb where name=? and id in (select uid from anothertable where score in (?,?,?))", cond)
assert.Equal([]interface{}{"caibirdme", 3.0, 5.8, 7.9}, vals)

slice 类型的值会根据 slice 的长度自动展开。

这种方式基本上就是手写 sql,非常便于 DBA review 同时也方便开发者进行复杂 sql 的调优。

对于关键系统,推荐使用这种方式

具体文档看 builder

Scanner

执行了数据库操作之后,要把返回的结果集和自定义的 struct 进行映射。Scanner 提供一个简单的接口通过反射来进行结果集和自定义类型的绑定:

type Person struct {
  Name string `ddb:"name"`
  Age int `ddb:"m_age"`
}

rows,err := db.Query("SELECT age as m_age,name from g_xxx where xxx")
defer rows.Close()

var students []Person

scanner.Scan(rows, &students)

for _,student := range students {
  fmt.Println(student)
}

scanner 进行反射时会使用结构体的 tag,如上所示,scanner 会把结果集中的 m_age 绑定到结构体的 Age 域上。默认使用的 tagName 是ddb:"xxx",你也可以自定义。

scanner.SetTagName("json")

type Person struct {
  Name string `json:"name"`
  Age int `json:"m_age"`
}

// ...
var student Person
scaner.Scan(rows, &student)

scaner.SetTagName 是全局设置,为了避免歧义,只允许设置一次,一般在初始化 DB 阶段进行此项设置

ScanMap

ScanMap 方法返回的是一个 map,有时候你可能不太想定义一个结构体去存你的中间结果,那么 ScanMap 或许比较有帮助

rows,_ := db.Query("select name,m_age from person")
result,err := scanner.ScanMap(rows)
for _,record := range result {
  fmt.Println(record["name"], record["m_age"])
}

注意:

  • 如果是使用 Scan 或者 ScanMap 的话,你必须在之后手动 close rows
  • 传给 Scan 的必须是引用
  • ScanClose 和 ScanMapClose 不需要手动 close rows

CLI Tool

除了以上 API,Gendry 还提供了一个命令行工具来进行代码生成,可以显著减少你的开发量。详见 gforge

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

文章
评论
84965 人气
更多

推荐作者

万事如意

文章 0 评论 0

微信用户

文章 0 评论 0

1649543945

文章 0 评论 0

华纳云

文章 0 评论 0

我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文