doc: 阅读golang sqlx文档
This commit is contained in:
@@ -1,3 +1,38 @@
|
|||||||
|
- [SQLX](#sqlx)
|
||||||
|
- [Getting Start with SQLite](#getting-start-with-sqlite)
|
||||||
|
- [Handle Types](#handle-types)
|
||||||
|
- [Connecting to database](#connecting-to-database)
|
||||||
|
- [Open Db and connect in same time](#open-db-and-connect-in-same-time)
|
||||||
|
- [Quering](#quering)
|
||||||
|
- [Exec](#exec)
|
||||||
|
- [bindvars](#bindvars)
|
||||||
|
- [Rebind](#rebind)
|
||||||
|
- [bindvars is only used for parameterization](#bindvars-is-only-used-for-parameterization)
|
||||||
|
- [Query](#query)
|
||||||
|
- [Query errors](#query-errors)
|
||||||
|
- [Connection Closed Scenes](#connection-closed-scenes)
|
||||||
|
- [Queryx](#queryx)
|
||||||
|
- [QueryRow](#queryrow)
|
||||||
|
- [QueryRowx](#queryrowx)
|
||||||
|
- [Get \& Select](#get--select)
|
||||||
|
- [scannable](#scannable)
|
||||||
|
- [Exec和Query在归还连接池上的差异](#exec和query在归还连接池上的差异)
|
||||||
|
- [Transactions](#transactions)
|
||||||
|
- [PreparedStatement](#preparedstatement)
|
||||||
|
- [PreparedStatement事务操作](#preparedstatement事务操作)
|
||||||
|
- [QueryHelper](#queryhelper)
|
||||||
|
- [`In` Queries](#in-queries)
|
||||||
|
- [`Named Queries`](#named-queries)
|
||||||
|
- [Advanced Scanning](#advanced-scanning)
|
||||||
|
- [struct embeded](#struct-embeded)
|
||||||
|
- [Scan Destination Safety](#scan-destination-safety)
|
||||||
|
- [Control Name Mapping](#control-name-mapping)
|
||||||
|
- [sqlx.DB.MapperFunc](#sqlxdbmapperfunc)
|
||||||
|
- [Slice Scan \& Map Scan](#slice-scan--map-scan)
|
||||||
|
- [Scanner/Valuer](#scannervaluer)
|
||||||
|
- [Connection Pool](#connection-pool)
|
||||||
|
|
||||||
|
|
||||||
# SQLX
|
# SQLX
|
||||||
## Getting Start with SQLite
|
## Getting Start with SQLite
|
||||||
为了安装sqlx和database driver,可以通过如下命令:
|
为了安装sqlx和database driver,可以通过如下命令:
|
||||||
@@ -238,6 +273,7 @@ err = db.Select(&names, "SELECT name FROM place LIMIT 10")
|
|||||||
Exec操作和Query操作在归还连接到连接池的时机有所不同:
|
Exec操作和Query操作在归还连接到连接池的时机有所不同:
|
||||||
- `Exec`: `Exec`方法在`server返回执行结果给client之后`,`client根据返回结果构建并返回sql.Result之前`,将会将连接返回给连接池
|
- `Exec`: `Exec`方法在`server返回执行结果给client之后`,`client根据返回结果构建并返回sql.Result之前`,将会将连接返回给连接池
|
||||||
- `Query`: `Query`方法和`Exec`方法不同,其返回信息中包含结果集,必须等待结果集`迭代完成`或`手动调用rows.Close`方法之后,才会归还连接给连接池
|
- `Query`: `Query`方法和`Exec`方法不同,其返回信息中包含结果集,必须等待结果集`迭代完成`或`手动调用rows.Close`方法之后,才会归还连接给连接池
|
||||||
|
- `QueryRow`:在返回的Row对象被Scan后,将会归还连接给数据库
|
||||||
|
|
||||||
## Transactions
|
## Transactions
|
||||||
为了使用事务,必须通过`DB.Begin()`方法创建事务,如下代码将`不会起作用`:
|
为了使用事务,必须通过`DB.Begin()`方法创建事务,如下代码将`不会起作用`:
|
||||||
@@ -297,5 +333,245 @@ tx.Exec(xxxx)
|
|||||||
|
|
||||||
对于PreparedStatement操作,如果想要将其和事务相关联,有如下两种使用方式:
|
对于PreparedStatement操作,如果想要将其和事务相关联,有如下两种使用方式:
|
||||||
- `如果statement此时尚未创建`,可以通过`tx.Prepare`来创建该连接
|
- `如果statement此时尚未创建`,可以通过`tx.Prepare`来创建该连接
|
||||||
- `如果Steement此时已经创建`,那么可以通过`tx.Stmt(dbStmt)`来获取新的Stmt,新Stmt会和事务所属连接绑定,通过新Stmt执行dml可以加入当前事务
|
- `如果Statement此时已经创建`,那么可以通过`tx.Stmt(dbStmt)`来获取新的Stmt,新Stmt会和事务所属连接绑定,通过新Stmt执行dml可以加入当前事务
|
||||||
|
|
||||||
|
## QueryHelper
|
||||||
|
`database/sql`并不会对传入的实际query text做任何处理,故而在编写部分语句时可能会较为困难。
|
||||||
|
|
||||||
|
### `In` Queries
|
||||||
|
由于`database/sql`并不会对query text做为何处理,而是直接将其传给driver,故而在处理`in`查询语句时将变得相当困难:
|
||||||
|
```sql
|
||||||
|
SELECT * FROM users WHERE level IN (?);
|
||||||
|
```
|
||||||
|
此处,可以通过`sqlx.In`方法来首先进行处理,示例如下:
|
||||||
|
```golang
|
||||||
|
var levels = []int{4, 6, 7}
|
||||||
|
query, args, err := sqlx.In("SELECT * FROM users WHERE level IN (?);", levels)
|
||||||
|
|
||||||
|
// sqlx.In returns queries with the `?` bindvar, we can rebind it for our backend
|
||||||
|
query = db.Rebind(query)
|
||||||
|
rows, err := db.Query(query, args...)
|
||||||
|
```
|
||||||
|
`sqlx.In`会将query text中所有和`args中slice类型argument相关联的bindvar`拓展到`slice`的长度,并且将args中的参数重新添加到新的argList中,示例如下所示:
|
||||||
|
```go
|
||||||
|
var query string
|
||||||
|
var args []interface{}
|
||||||
|
query, args, err = sqlx.In("select * from location where cities in (?) and code = ? and id in (?)", []string{"BEIJING", "NEW_YORK"},"asahi", []uint64{1, 3})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
query = db.Rebind(query)
|
||||||
|
log.Printf("query transformed: %s\n", query)
|
||||||
|
log.Printf("args transformed: %v, %v, %v, %v, %v\n", args[0], args[1], args[2], args[3], args[4])
|
||||||
|
```
|
||||||
|
其对应结果为
|
||||||
|
```go
|
||||||
|
2025/06/21 16:30:38 query transformed: select * from location where cities in (?, ?) and code = ? and id in (?, ?)
|
||||||
|
2025/06/21 16:30:38 args transformed: BEIJING, NEW_YORK, asahi, 1, 3
|
||||||
|
```
|
||||||
|
其中,包含`[]string{"BEIJING", "NEW_YORK"},"asahi", []uint64{1, 3})`三个元素的args被重新转换为了包含`BEIJING, NEW_YORK, asahi, 1, 3`五个元素的argList。
|
||||||
|
|
||||||
|
### `Named Queries`
|
||||||
|
可以使用`named bindvar`语法来将struct field/map keys和variable绑定在一起,struct field命名遵循`StructScan`。
|
||||||
|
|
||||||
|
关联方法如下:
|
||||||
|
- `NamedQuery(...) (*sqlx.Rows, error)`:和Queryx类似,但是支持named bindvars
|
||||||
|
- `NamedExec(...) (sql.Result, error)`:和Exec类似,但是支持named bindvars
|
||||||
|
|
||||||
|
除此之外,还有额外的类型:
|
||||||
|
- `NamedStmt`: 和`sqlx.Stmt`类似,支持`prepared with named bindvars`
|
||||||
|
|
||||||
|
```go
|
||||||
|
// named query with a struct
|
||||||
|
p := Place{Country: "South Africa"}
|
||||||
|
rows, err := db.NamedQuery(`SELECT * FROM place WHERE country=:country`, p)
|
||||||
|
|
||||||
|
// named query with a map
|
||||||
|
m := map[string]interface{}{"city": "Johannesburg"}
|
||||||
|
result, err := db.NamedExec(`SELECT * FROM place WHERE city=:city`, m)
|
||||||
|
```
|
||||||
|
`Named query execution`和`Named preparation`针对struct和map都起作用,如果希望在所有的query verbs都支持named queries,可以使用named statement
|
||||||
|
```go
|
||||||
|
p := Place{TelephoneCode: 50}
|
||||||
|
pp := []Place{}
|
||||||
|
|
||||||
|
// select all telcodes > 50
|
||||||
|
nstmt, err := db.PrepareNamed(`SELECT * FROM place WHERE telcode > :telcode`)
|
||||||
|
err = nstmt.Select(&pp, p)
|
||||||
|
```
|
||||||
|
`Named query`会将`:param`语法转化为底层数据库支持的`bindvar`语法,并且在执行时执行mapping,故而其对sqlx支持的所有数据库都适用。
|
||||||
|
|
||||||
|
可以使用`sqlx.Named`方式来将`:param`语法转化为`?`形式,并后续和`sqlx.In`相结合,示例如下:
|
||||||
|
```go
|
||||||
|
var query string
|
||||||
|
params := map[string]any{
|
||||||
|
"code": "ASAHI",
|
||||||
|
"cities": []string{"BEIJING", "NEWYORK"},
|
||||||
|
"id": []uint64{1, 3},
|
||||||
|
}
|
||||||
|
var args []any
|
||||||
|
query, args, err = sqlx.Named("select * from location where cities in (:cities) and code = :code and id in (:id)",
|
||||||
|
params)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
log.Printf("query after named transformation: %s\n", query)
|
||||||
|
log.Printf("args after named transformation: %v\n", args)
|
||||||
|
```
|
||||||
|
上述的输出为
|
||||||
|
```go
|
||||||
|
2025/06/21 16:58:39 query after named transformation: select * from location where cities in (?) and code = ? and id in (?)
|
||||||
|
2025/06/21 16:58:39 args after named transformation: [[BEIJING NEWYORK] ASAHI [1 3]]
|
||||||
|
```
|
||||||
|
其中,`sqlx.Named`会将`struct/map`参数转化为`argList`,并且将`named query`转化为`query with bindvar syntax`的形式,后续,`query with bindvar syntax`还可以结合`sqlx.In`来使用
|
||||||
|
```go
|
||||||
|
arg := map[string]interface{}{
|
||||||
|
"published": true,
|
||||||
|
"authors": []{8, 19, 32, 44},
|
||||||
|
}
|
||||||
|
query, args, err := sqlx.Named("SELECT * FROM articles WHERE published=:published AND author_id IN (:authors)", arg)
|
||||||
|
query, args, err := sqlx.In(query, args...)
|
||||||
|
query = db.Rebind(query)
|
||||||
|
db.Query(query, args...)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Scanning
|
||||||
|
Struct Scan支持embeded struct,并且对`embeded attribute和method access`使用和golang相同的优先顺序:
|
||||||
|
- 即struct scan在执行过程中,如果struct A嵌套了struct B,并且A和B中都拥有名为`ID`的字段,那么将query结果扫描到A中时,query result中的`id`将会被优先映射到A中的`ID`字段中,而`B.ID`字段则会被忽略
|
||||||
|
|
||||||
|
```go
|
||||||
|
type struct B {
|
||||||
|
ID string
|
||||||
|
xxx
|
||||||
|
}
|
||||||
|
|
||||||
|
type struct A {
|
||||||
|
B
|
||||||
|
ID string
|
||||||
|
xxx
|
||||||
|
}
|
||||||
|
|
||||||
|
a := A{}
|
||||||
|
db.Select(&a, "select id from table_a where xxx")
|
||||||
|
// 会被映射到A中名为ID的struct field中,B.ID在struct scan时会被忽略
|
||||||
|
```
|
||||||
|
### struct embeded
|
||||||
|
在日常使用中,通常会使用`struct embeded`通常用作`将多张tables共享的公共field抽取到一个embeded struct中`,示例如下:
|
||||||
|
```go
|
||||||
|
type AutoIncr struct {
|
||||||
|
ID uint64
|
||||||
|
Created time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type Place struct {
|
||||||
|
Address string
|
||||||
|
AutoIncr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
Name string
|
||||||
|
AutoIncr
|
||||||
|
}
|
||||||
|
```
|
||||||
|
上述示例中,person和place都支持在struct scan时接收id和created字段。并且,`其是递归的`。
|
||||||
|
```go
|
||||||
|
type Employee struct {
|
||||||
|
BossID uint64
|
||||||
|
EmployeeID uint64
|
||||||
|
Person
|
||||||
|
}
|
||||||
|
```
|
||||||
|
上述示例中,`Employee`中包含了来自`Person`中的`Name`和`AutoIncr ID/Created`字段。
|
||||||
|
|
||||||
|
在sqlx构建field name和field address的映射时,在将数据扫描到struct之前,无法知晓name是否会在struct tree中遭遇两次。
|
||||||
|
|
||||||
|
> field name和field address的映射关系,其表示如下:
|
||||||
|
> - field name: 该struct field对应的name
|
||||||
|
> - field address: 该struct field对应的address
|
||||||
|
>
|
||||||
|
> 例如:
|
||||||
|
> ```go
|
||||||
|
> type A struct {
|
||||||
|
> ID int `db:"id"`
|
||||||
|
> }
|
||||||
|
> 其中,field name为`id`,而field address则为`&a.ID`所代表的地址
|
||||||
|
>
|
||||||
|
> field name到field address的映射关系将会在后续structScan时使用
|
||||||
|
|
||||||
|
并不像go中`ambiguous selectors`会导致异常,`struct Scan将会选择遇到的第一个拥有相同name的field`。以最外层的struct作为tree root,embeded struct作为child root,其遵循`shallowest, top-most`的匹配原则,即`离root最近,且在同一struct定义中,定义靠上的struct field更优先`。
|
||||||
|
|
||||||
|
例如,在如下定义中:
|
||||||
|
```go
|
||||||
|
type PersonPlace struct {
|
||||||
|
Person
|
||||||
|
Place
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scan Destination Safety
|
||||||
|
默认情况下,如果column并没有匹配到对应的field,将会返回一个error。
|
||||||
|
|
||||||
|
但是,如果想要在column未匹配到field的情况下,不返回error,那么可以使用`db.Unsafe`方法,示例如下:
|
||||||
|
```go
|
||||||
|
var p Person
|
||||||
|
// err here is not nil because there are no field destinations for columns in `place`
|
||||||
|
err = db.Get(&p, "SELECT * FROM person, place LIMIT 1;")
|
||||||
|
|
||||||
|
// this will NOT return an error, even though place columns have no destination
|
||||||
|
udb := db.Unsafe()
|
||||||
|
err = udb.Get(&p, "SELECT * FROM person, place LIMIT 1;")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Control Name Mapping
|
||||||
|
被用作target field的struct field首字母必须大写,从而使该field可以被sqlx访问。
|
||||||
|
|
||||||
|
因为struct field的首字母为大写,故而sqlx使用了`NameMapper`,对field name执行了`strings.ToLower`方法,并将toLower之后的fieldname和rows result相匹配。
|
||||||
|
|
||||||
|
sqlx除了上述默认的匹配方法外,还支持自定义的匹配。
|
||||||
|
|
||||||
|
#### sqlx.DB.MapperFunc
|
||||||
|
最简单的自定义匹配的方式是使用`sqlx.DB.MapperFunc`,其接收一个`func(string) string`参数。使用示例如下:
|
||||||
|
```go
|
||||||
|
// if our db schema uses ALLCAPS columns, we can use normal fields
|
||||||
|
db.MapperFunc(strings.ToUpper)
|
||||||
|
|
||||||
|
// suppose a library uses lowercase columns, we can create a copy
|
||||||
|
copy := sqlx.NewDb(db.DB, db.DriverName())
|
||||||
|
copy.MapperFunc(strings.ToLower)
|
||||||
|
```
|
||||||
|
### Slice Scan & Map Scan
|
||||||
|
除了structScan之外,sqlx还支持slice scan和map scan的形式,示例如下:
|
||||||
|
```go
|
||||||
|
rows, err := db.Queryx("SELECT * FROM place")
|
||||||
|
for rows.Next() {
|
||||||
|
// cols is an []interface{} of all of the column results
|
||||||
|
cols, err := rows.SliceScan()
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.Queryx("SELECT * FROM place")
|
||||||
|
for rows.Next() {
|
||||||
|
results := make(map[string]interface{})
|
||||||
|
err = rows.MapScan(results)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### Scanner/Valuer
|
||||||
|
通过sql.Scanner/driver.Valuer接口,可以实现自定义类型的读取/写入。
|
||||||
|
|
||||||
|
## Connection Pool
|
||||||
|
db对象将会管理一个连接池,有两种方式可以控制连接池的大小:
|
||||||
|
- `DB.SetMaxIdleConns(n int)`
|
||||||
|
- `DB.SetMaxOpenConns(n int)`
|
||||||
|
|
||||||
|
默认情况下,连接池会无限增长,在任何时刻如果连接池中没有空闲的连接都会创建新连接。可以通过`DB.SetMaxOpenConns(n int)`来控制连接池中的最大连接数目。
|
||||||
|
|
||||||
|
如果连接池中的连接没有在使用,那么会被标注为idle状态,并且在不再被需要时关闭。为了避免频繁的关闭和创建连接,可以使用`DB.SetMaxIdleConns`来设置最大的空闲连接数量,从而适配业务场景的查询负载。
|
||||||
|
|
||||||
|
为了避免长期持有连接,需要确保如下条目:
|
||||||
|
- 确保针对每个Row Object执行了Scan操作
|
||||||
|
- 确保对于Rows object,要么调用了Close方法,要么调用Next`进行了完全迭代`
|
||||||
|
- 确保每个事务都调用了commit或rollback
|
||||||
|
|
||||||
|
如果上述描述中的某一项没有被保证,那么连接可能被长期持有直到发生垃圾回收,为了弥补被持有的连接,需要创建更多连接。
|
||||||
|
|
||||||
|
> `Rows.Close`方法可以被安全的多次调用,故而当rows object不再被需要时,应当调用该方法,无需考虑其是否已经被调用过。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user