Skip to content

Commit 2e2f73f

Browse files
committed
Update README.md
1 parent 6ebc527 commit 2e2f73f

File tree

2 files changed

+141
-24
lines changed

2 files changed

+141
-24
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- 支持 全局/单独 指定 SQL 语句的连接和读写超时;
1616
- 提供了支持 `嵌套事务` 的事务 API;
1717
- 能根据目标数据库的配置,自动配置 `SetConnMaxLifetime` (目前仅支持 MySQL);
18+
- 支持传递为 `in 子句` 传递 `slice` 类型的参数 (目前仅支持 MySQL);
1819

1920
## 来个 Demo
2021

@@ -53,7 +54,7 @@ func main() {
5354

5455
selectionDemo()
5556
ormDemo()
56-
ormWithTypeChange()
57+
ormWithFieldConvert()
5758
transactionDemo()
5859
timeoutDemo()
5960
}
@@ -90,7 +91,10 @@ func selectionDemo() {
9091
fmt.Println(name.(string)) // Output: rui
9192

9293
// 如果喜欢标准库风格,这里也提供了增强版本的 sql.Rows,支持 SliceScan、MapScan。
93-
rows := dbClient.MustRows("SELECT Name, now() FROM demo WHERE Name IN (@p1, @p2)", "rui", "bao")
94+
// 注意:
95+
// - 这里 in 的参数适用了 slice 的方式,这个特性目前仅支持 MySQL,针对 SQL Server 暂时需要自行展开;
96+
// - 如果 slice 为空,会被解析为 NULL ,这会导致 in/not in 语句均为 false;
97+
rows := dbClient.MustRows("SELECT Name, now() FROM demo WHERE Name IN (@p1)", []any{"rui", "bao"})
9498
for rows.Next() {
9599
// SliceScan 会自动判断列数及列类型,用 []any 方式返回。
96100
if dataSlice, err := rows.SliceScan(); err != nil {

example/main.go

Lines changed: 135 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,62 @@ import (
44
"context"
55
"fmt"
66
"log"
7+
"strconv"
8+
"strings"
79
"time"
810

911
"github.com/bunnier/sqlmer"
1012
"github.com/bunnier/sqlmer/mysql"
1113
)
1214

13-
func main() {
14-
var dbClient sqlmer.DbClient // 这是本库的主接口,统一了各种数据库的 API 操作。
15-
var err error // 本库同时提供了 error/panic 两套 API,为了 demo 更为简洁,后续主要通过 panic(Must) 版本 API 演示。
15+
var dbClient sqlmer.DbClient // 这是本库的主接口,统一了各种数据库的 API 操作。
16+
var err error // 本库同时提供了 error/panic 两套 API,为了 demo 更为简洁,后续主要通过 panic(Must) 版本 API 演示。
1617

18+
func init() {
1719
// 这里使用 MySQL 做示范,SQL Server 也提供了一致的 API 和相应的参数解析逻辑。
1820
if dbClient, err = mysql.NewMySqlDbClient(
19-
"test:test@tcp(127.0.0.1:1433)/test",
21+
"test:testpwd@tcp(127.0.0.1:3306)/test",
2022
sqlmer.WithConnTimeout(time.Second*30), // 连接超时。
2123
sqlmer.WithExecTimeout(time.Second*30), // 读写超时(执行语句时候,如果没有指定超时时间,默认用这个)。
2224
); err != nil {
2325
log.Fatal(err)
2426
}
27+
}
28+
29+
func main() {
30+
prepare()
31+
defer purge()
32+
33+
selectionDemo()
34+
ormDemo()
35+
ormWithFieldConvert()
36+
transactionDemo()
37+
timeoutDemo()
38+
}
2539

40+
func prepare() {
2641
// 创建/删除 测试表。
2742
dbClient.MustExecute(`
28-
CREATE TABLE demo(
43+
CREATE TABLE IF NOT EXISTS demo (
2944
Id int(11) NOT NULL AUTO_INCREMENT,
3045
Name VARCHAR(10) NOT NULL,
3146
Age INT NOT NULL,
47+
Scores VARCHAR(200) NOT NULL,
3248
PRIMARY KEY (Id),
33-
KEY demo (Id))`)
34-
defer dbClient.MustExecute("DROP TABLE demo")
49+
KEY demo (Id)
50+
)
51+
`)
3552

36-
// 通过 context 设置超时时间。。
37-
ctx, _ := context.WithTimeout(context.Background(), time.Second*1)
38-
if _, err = dbClient.ExecuteContext(ctx, "SELECT sleep(3)"); err != nil {
39-
fmt.Println("timeout: " + err.Error()) // 预期内的超时~
40-
}
53+
// 索引方式插入数据,@p1..@pN,分别对应第 1..n 个参数。
54+
dbClient.MustExecute("INSERT INTO demo(Name, Age, Scores) VALUES(@p1, @p2, @p3)", "rui", 1, "SCORES:1,3,5,7")
55+
dbClient.MustExecute("INSERT INTO demo(Name, Age, Scores) VALUES(@p1, @p2, @p3)", "bao", 2, "SCORES:2,4,6,8")
56+
}
4157

42-
// 索引方式插入数据,@p1..@pn,分别对应第 1..n 个参数。
43-
dbClient.MustExecute("INSERT INTO demo(Name, Age) VALUES(@p1, @p2)", "rui", 1)
44-
dbClient.MustExecute("INSERT INTO demo(Name, Age) VALUES(@p1, @p2)", "bao", 2)
58+
func purge() {
59+
dbClient.MustExecute("DROP TABLE demo")
60+
}
4561

62+
func selectionDemo() {
4663
// 命名参数查询数据,命名参数采用 map,key 为 sql 语句 @ 之后的参数名,value 为值。
4764
dataMap := dbClient.MustGet("SELECT * FROM demo WHERE Name=@name", map[string]any{"name": "rui"})
4865
fmt.Println(dataMap) // Output: map[Age:1 Id:1 Name:rui]
@@ -52,7 +69,10 @@ func main() {
5269
fmt.Println(name.(string)) // Output: rui
5370

5471
// 如果喜欢标准库风格,这里也提供了增强版本的 sql.Rows,支持 SliceScan、MapScan。
55-
rows := dbClient.MustRows("SELECT Name, now() FROM demo WHERE Name IN (@p1, @p2)", "rui", "bao")
72+
// 注意:
73+
// - 这里 in 的参数适用了 slice 的方式,这个特性目前仅支持 MySQL,针对 SQL Server 暂时需要自行展开;
74+
// - 如果 slice 为空,会被解析为 NULL ,这会导致 in/not in 语句均为 false;
75+
rows := dbClient.MustRows("SELECT Name, now() FROM demo WHERE Name IN (@p1)", []any{"rui", "bao"})
5676
for rows.Next() {
5777
// SliceScan 会自动判断列数及列类型,用 []any 方式返回。
5878
if dataSlice, err := rows.SliceScan(); err != nil {
@@ -71,21 +91,114 @@ func main() {
7191
if err = rows.Close(); err != nil {
7292
log.Fatal(err)
7393
}
94+
}
95+
96+
// 演示如何使用轻量化 ORM 功能。将数据库的行,映射到 Go struct 。
97+
//
98+
// 由于 Go 语言的限制, struct 的字段必须是大写字母开头的,可能和数据库命名规范不一致。
99+
// 在 ORM 的映射中,可以支持驼峰和下划线分割的名称的首字母模糊匹配,例如:
100+
// struct 字段 GoodName 可以自动匹配到数据库字段 GoodName/goodName/good_name 。
101+
func ormDemo() {
102+
// 用于表示对应行的数据的类型。
103+
type Row struct {
104+
Id int
105+
Name string
106+
Age int
107+
Scores string
108+
}
109+
110+
// 轻量化 ORM 的 API 定义在 DbClientEx 里,需要扩展( Extend ) DbClient 得到。
111+
clientEx := sqlmer.Extend(dbClient)
112+
113+
// 获取一行。
114+
var row Row
115+
clientEx.MustGetStruct(&row, "SELECT * FROM demo WHERE Id=1")
116+
fmt.Printf("%v\n", row) // Output: {1 rui 1 1,3,5,7}
117+
118+
// 获取一个列表。
119+
//
120+
// 由于 Golang 不支持方法级的泛型,这里需要通过第一个参数传入一个模板(这里是 Row{} ),指定需要将数据行映射到什么类型;
121+
// API 返回模板类型的 slice ,以 any 表示,需要再进行类型转换(这里是 []Row )。
122+
rows := clientEx.MustListOf(Row{}, "SELECT * FROM demo").([]Row)
123+
fmt.Println("Rows:")
124+
for _, v := range rows {
125+
fmt.Printf("%v\n", v)
126+
}
127+
// Output:
128+
// Rows:
129+
// {1 rui 1 SCORES:1,3,5,7}
130+
// {2 bao 2 SCORES:2,4,6,8}
74131

132+
// 模板可以是 struct 也可以是其指针,指针的写法:
133+
// clientEx.MustListOf(new(Row), "SELECT * FROM demo").([]*Row)
134+
}
135+
136+
// 演示如何在 ORM 过程中,如果 struct 的目标字段的类型和数据库的类型不能兼容时,如何通过代码定制转换过程。
137+
func ormWithFieldConvert() {
138+
// 目标类型。
139+
type Row struct {
140+
Id int
141+
Name string
142+
Age int
143+
Scores []int
144+
}
145+
146+
// 这里利用 Golang 的 struct 内嵌特性,让外层 struct 的同名字段隐藏掉内层的,
147+
// ORM 赋值过程中, Scores 会赋值到外层,先用兼容的类型(这里是 string )将数据库的值取出来。
148+
wrapper := &struct {
149+
Row
150+
Scores string
151+
}{}
152+
sqlmer.Extend(dbClient).MustGetStruct(wrapper, "SELECT * FROM demo WHERE Id=1")
153+
154+
// 在已经取到值的基础上,用一段代码,将处理后的值赋值给最终的目标。
155+
// 这里 Scores 字段的格式是 SCORES: N1,N2,N3,... ,我们的目标格式是将数字部分转换为 []int 。
156+
row := wrapper.Row
157+
scores := strings.TrimPrefix(wrapper.Scores, "SCORES:")
158+
for _, v := range strings.Split(scores, ",") {
159+
i, _ := strconv.Atoi(v)
160+
row.Scores = append(row.Scores, i)
161+
}
162+
163+
fmt.Printf("%v\n", row) // Output: {1 rui 1 [1 3 5 7]}
164+
}
165+
166+
// 演示如何使用数据库事务。
167+
func transactionDemo() {
75168
rowNum, _ := dbClient.MustScalar("SELECT count(1) FROM demo")
76169
fmt.Println(rowNum) // Output: 2
77170

78-
trans := dbClient.MustCreateTransaction() // 事务操作也支持和 DbClient 几乎一致的 API。
171+
// CreateTransaction 返回一个 TransactionKeeper ,
172+
// 它实现了 DbClient ,所以数据库操作方式和一般的 DbClient 完全一致。
173+
trans := dbClient.MustCreateTransaction()
174+
175+
// 如果 TransactionKeeper.Commit/MustCommit 没有被调用,则 Close 操作会回滚事务;
176+
// 若事务已提交,则 Close 操作仅关闭连接。
177+
defer trans.MustClose()
178+
79179
trans.MustExecute("DELETE FROM demo WHERE Id=1")
80180

81-
embeddedTrans := trans.MustCreateTransaction() // 支持嵌套事务。
82-
embeddedTrans.MustExecute("DELETE FROM demo WHERE Id=2")
83-
embeddedTrans.MustCommit()
84-
embeddedTrans.MustClose() // 注意:嵌套事务也需要 Close。
181+
// 支持使用嵌套事务。
182+
func() {
183+
embeddedTrans := trans.MustCreateTransaction()
184+
defer embeddedTrans.MustClose() // 注意:嵌套事务也需要 Close 。
185+
186+
embeddedTrans.MustExecute("DELETE FROM demo WHERE Id=2")
187+
embeddedTrans.MustCommit()
188+
}()
85189

190+
// 提交外层的事务。
86191
trans.MustCommit()
87-
trans.MustClose()
88192

89193
rowNum, _ = dbClient.MustScalar("SELECT count(1) FROM demo")
90194
fmt.Println(rowNum) // Output: 0
91195
}
196+
197+
// 演示如何设置超时时间。 DbClient 中的方法,都有一个 Context 版,支持传入 Context 以设置超时。
198+
// 如 Execute 对应 ExecuteContext 。
199+
func timeoutDemo() {
200+
ctx, _ := context.WithTimeout(context.Background(), time.Second*1)
201+
if _, err = dbClient.ExecuteContext(ctx, "SELECT sleep(3)"); err != nil {
202+
fmt.Println("timeout: " + err.Error()) // 预期内的超时~
203+
}
204+
}

0 commit comments

Comments
 (0)