@@ -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