golang非侵入式SQL构建与数据绑定插件库gendry的使用
Golang非侵入式SQL构建与数据绑定插件库Gendry的使用
Gendry是一个帮助操作数据库的Go库。基于go-sql-driver/mysql
,它提供了一系列简单但有用的工具来为调用标准库database/sql
中的方法准备参数。
Gendry的组成部分
Gendry由三个独立的部分组成,可以单独使用每个部分:
- Manager - 用于初始化数据库连接池
- Builder - 用于构建SQL
- Scanner - 用于数据绑定
- CLI工具 - 代码生成工具
Manager
Manager用于初始化数据库连接池(即sql.DB
)。你可以为支持的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所做的只是生成dataSourceName
,其格式为:
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
Builder
Builder用于构建SQL。手动编写SQL直观但难以维护。对于where in
,如果in
集合中有大量元素,编写起来非常困难。
Builder不是一个ORM。事实上,我们创建Gendry的最重要原因之一是我们不喜欢ORM。所以Gendry只提供了一些简单的API来帮助你构建SQL:
where := map[string]interface{}{
"city": []string{"beijing", "shanghai"},
"score": 5,
"age >": 35,
"address": builder.IsNotNull,
"_or": []map[string]interface{}{
{
"x1": 11,
"x2 >=": 45,
},
{
"x3": "234",
"x4 <>": "tx2",
},
},
"_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 some_table WHERE (((x1=? AND x2>=?) OR (x3=? AND x4!=?)) AND score=? AND city IN (?,?) AND age>? AND address IS NOT NULL) GROUP BY department ORDER BY bonus DESC
// values = []interface{}{11, 45, "234", "tx2", 5, "beijing", "shanghai", 35}
rows, err := db.Query(cond, values...)
// 支持builder.Raw在where和update中
where := map[string]interface{}{"gmt_create <": builder.Raw("gmt_modified")}
cond, values, err := builder.BuildSelect(table, where, selectFields)
// SELECT * FROM x WHERE gmt_create < gmt_modified
update = map[string]interface{}{
"code": builder.Raw("VALUES(code)"), // mysql 8.x builder.Raw("new.code")
"name": builder.Raw("VALUES(name)"), // mysql 8.x builder.Raw("new.name")
}
cond, values, err := builder.BuildInsertOnDuplicate(table, data, update)
// INSERT INTO country (id, code, name) VALUES (?,?,?),(?,?,?),(?,?,?)
// ON DUPLICATE KEY UPDATE code=VALUES(code),name=VALUES(name)
在where
参数中,in
操作符会根据值类型(reflect.Slice)自动添加。
此外,该库提供了一个有用的API来执行聚合查询,如count、sum、max、min、avg:
where := map[string]interface{}{
"score > ": 100,
"city": []interface{}{"Beijing", "Shijiazhuang"},
}
// 支持AggregateSum, AggregateMax, AggregateMin, AggregateCount, AggregateAvg
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)
Scanner
对于MySQL的每个响应,你可能希望将其映射到你定义良好的结构中。Scanner提供了一个直接的API来做到这一点,它基于反射:
标准库方式
type Person struct {
Name string
Age int
}
rows, err := db.Query("SELECT age as m_age, name from g_xxx where xxx")
defer rows.Close()
var students []Person
for rows.Next() {
var student Person
rows.Scan(student.Age, student.Name)
students = append(students, student)
}
使用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)
实现ByteUnmarshaler
接口的类型将接管相应的解组工作:
type ByteUnmarshaler interface {
UnmarshalByte(data []byte) error
}
示例:
type human struct {
Age int `ddb:"ag"`
Extra *extraInfo `ddb:"ext"`
}
type extraInfo struct {
Hobbies []string `json:"hobbies"`
LuckyNumber int `json:"ln"`
}
func (ext *extraInfo) UnmarshalByte(data []byte) error {
return json.Unmarshal(data, ext)
}
// 如果表中ext列的类型是varchar(存储合法的json字符串)或json(mysql5.7)
var student human
err := scanner.Scan(rows, &student)
// ...
ScanMap
rows, _ := db.Query("select name, age as m_age from person")
result, err := scanner.ScanMap(rows)
for _, record := range result {
fmt.Println(record["name"], record["m_age"])
}
ScanMap从rows中扫描数据并返回一个[]map[string]interface{}
。
注意事项
- 如果不使用ScanXXXClose,不要忘记关闭rows
- Scan的第二个参数必须是一个引用
更多关于golang非侵入式SQL构建与数据绑定插件库gendry的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang非侵入式SQL构建与数据绑定插件库gendry的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang 非侵入式 SQL 构建与数据绑定库 Gendry 使用指南
Gendry 是一个轻量级的 Go 语言 SQL 构建器和数据绑定库,它提供了一种非侵入式的方式来构建 SQL 查询并将查询结果绑定到结构体。下面我将详细介绍 Gendry 的核心功能和使用方法。
1. Gendry 主要特性
- 非侵入式设计:不需要在结构体上添加标签
- 支持条件构建 (WHERE, LIMIT, ORDER BY 等)
- 自动将查询结果映射到结构体
- 支持事务操作
- 兼容标准库
database/sql
- 支持 MySQL 方言
2. 安装 Gendry
go get github.com/didi/gendry
3. 基本使用方法
3.1 初始化连接
import (
"database/sql"
"github.com/didi/gendry/builder"
"github.com/didi/gendry/scanner"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
if err != nil {
panic(err)
}
defer db.Close()
// 设置 scanner 默认使用 db 连接
scanner.SetDefault(db)
}
3.2 查询构建与执行
简单查询
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
// 构建查询
where := map[string]interface{}{
"age >": 18,
"name": "John",
}
selectFields := []string{"id", "name", "age"}
cond, vals, err := builder.BuildSelect("users", where, selectFields)
if err != nil {
panic(err)
}
// 执行查询
rows, err := db.Query(cond, vals...)
if err != nil {
panic(err)
}
// 结果绑定
var users []User
err = scanner.Scan(rows, &users)
if err != nil {
panic(err)
}
插入数据
data := map[string]interface{}{
"name": "Alice",
"age": 25,
}
// 构建插入语句
cond, vals, err := builder.BuildInsert("users", []map[string]interface{}{data})
if err != nil {
panic(err)
}
// 执行插入
result, err := db.Exec(cond, vals...)
if err != nil {
panic(err)
}
lastInsertID, err := result.LastInsertId()
if err != nil {
panic(err)
}
更新数据
where := map[string]interface{}{
"id": 1,
}
update := map[string]interface{}{
"age": 30,
"name": "Bob",
}
// 构建更新语句
cond, vals, err := builder.BuildUpdate("users", where, update)
if err != nil {
panic(err)
}
// 执行更新
_, err = db.Exec(cond, vals...)
if err != nil {
panic(err)
}
删除数据
where := map[string]interface{}{
"id": 1,
}
// 构建删除语句
cond, vals, err := builder.BuildDelete("users", where)
if err != nil {
panic(err)
}
// 执行删除
_, err = db.Exec(cond, vals...)
if err != nil {
panic(err)
}
3.3 高级查询功能
复杂条件查询
where := map[string]interface{}{
"age in": []int{18, 20, 22},
"name like": "%Smith%",
"or": map[string]interface{}{
"status": 1,
"vip": true,
},
"_orderby": "age desc",
"_limit": []uint{0, 10},
}
cond, vals, err := builder.BuildSelect("users", where, []string{"*"})
if err != nil {
panic(err)
}
var users []User
err = scanner.ScanQuery(db, cond, vals, &users)
if err != nil {
panic(err)
}
聚合查询
where := map[string]interface{}{
"age >": 18,
}
cond, vals, err := builder.BuildSelect("users", where, []string{"count(*) as count", "avg(age) as avg_age"})
if err != nil {
panic(err)
}
var result struct {
Count int `json:"count"`
AvgAge float64 `json:"avg_age"`
}
err = scanner.ScanQuery(db, cond, vals, &result)
if err != nil {
panic(err)
}
3.4 事务支持
tx, err := db.Begin()
if err != nil {
panic(err)
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
// 在事务中执行多个操作
data1 := map[string]interface{}{"name": "Alice", "age": 25}
cond, vals, err := builder.BuildInsert("users", []map[string]interface{}{data1})
if err != nil {
return err
}
_, err = tx.Exec(cond, vals...)
if err != nil {
return err
}
data2 := map[string]interface{}{"name": "Bob", "age": 30}
cond, vals, err = builder.BuildInsert("users", []map[string]interface{}{data2})
if err != nil {
return err
}
_, err = tx.Exec(cond, vals...)
if err != nil {
return err
}
4. 使用建议
- 对于简单的 CRUD 操作,Gendry 提供了非常便捷的 API
- 复杂查询建议还是使用原生 SQL 或 ORM 框架
- 注意 SQL 注入问题,Gendry 使用参数化查询来防止注入
- 对于大量数据的批量操作,考虑使用专门的批量插入方法
5. 总结
Gendry 是一个轻量且灵活的 SQL 构建器和数据绑定库,特别适合那些不想使用全功能 ORM 但又需要比原生 SQL 更方便的操作的场景。它的非侵入式设计使得代码更加简洁,同时保持了良好的性能。
对于简单的数据库操作项目,Gendry 是一个不错的选择,但对于复杂的业务逻辑和关系映射,可能需要考虑更成熟的 ORM 框架如 GORM 或 XORM。