gendry/translation/zhcn/README.md
enzeChen ea1a1e6056 add builder.OmitEmpty (#37)
* add builder.OmitEmpty

* fix comment, increase support for zero length omit of slice, map
2019-03-08 09:57:20 +08:00

174 lines
6.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Gendry
[![Build Status](https://www.travis-ci.org/didi/Gendry.svg?branch=master)](https://www.travis-ci.org/didi/Gendry)
[![Hex.pm](https://img.shields.io/hexpm/l/plug.svg)](https://github.com/didi/Gendry/blob/master/LICENSE)
**Gendry**是一个用于辅助操作数据库的Go包。基于`go-sql-driver/mysql`,它提供了一系列的方法来为你调用标准库`database/sql`中的方法准备参数。
**Gendery**主要分为3个独立的部分你可以单独使用任何一个部分
* [manager](#manager)
* [builder](#builder)
* [scanner](#scanner)
* [CLI Tool](#tools)
<h3 id="manager">Manager</h3>
manager主要用来初始化连接池(也就是`sql.DB`对象)设置各种参数因此叫manager。你可以设置任何`go-sql-driver/mysql`驱动支持的参数。
初始化连接池时,代码如下:
``` go
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&...&paramN=valueN]
```
manager是基于数据库驱动`go-mysql-driver/mysql`而开发的manager支持了几乎所有该驱动支持的参数设置。具体用法看manager的README。
<h3 id="builder">Builder</h3>
builder顾名思义就是构建生成sql语句。手写sql虽然直观简单但是可维护性差最主要的是硬编码容易出错。而且如果遇到大where in查询而in的集合内容又是动态的这就非常麻烦了。
builder不是一个ORM我们开发Gendry的重要原因之一就是不喜欢ORM,它只是提供简单的API帮你生成sql语句
```go
where := map[string]interface{}{
"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 map中的零值可以使用 builder.OmitEmpty
``` go
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
```go
where := map[string]interface{}{
"score > ": 100,
"city in": []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`将会派上用场:
```go
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](../../builder/README.md)
<h3 id="scanner">Scanner</h3>
执行了数据库操作之后要把返回的结果集和自定义的struct进行映射。Scanner提供一个简单的接口通过反射来进行结果集和自定义类型的绑定:
```go
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"`,你也可以自定义。
``` go
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或许比较有帮助
```go
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
<h3 id="tools">CLI Tool</h3>
除了以上APIGendry还提供了一个命令行工具来进行代码生成可以显著减少你的开发量。详见[gforge](https://github.com/caibirdme/gforge)
---
* 如果有任何问题,乐意为你解答
* 有任何功能上的需求,也欢迎提出来