Go 標準庫提供的數據庫接口database/sql
比較底層,使用它來操做數據庫很是繁瑣,並且容易出錯。於是社區開源了很多第三方庫,如上一篇文章中的sqlc
工具,還有各式各樣的 ORM (Object Relational Mapping,對象關係映射庫),如gorm
和xorm
。本文介紹xorm
。xorm
是一個簡單但強大的 Go 語言 ORM 庫,使用它能夠大大簡化咱們的數據庫操做。mysql
先安裝:git
$ go get xorm.io/xorm
因爲須要操做具體的數據庫(本文中咱們使用 MySQL),須要安裝對應的驅動:github
$ go get github.com/go-sql-driver/mysql
使用:golang
package main import ( "log" "time" _ "github.com/go-sql-driver/mysql" "xorm.io/xorm" ) type User struct { Id int64 Name string Salt string Age int Passwd string `xorm:"varchar(200)"` Created time.Time `xorm:"created"` Updated time.Time `xorm:"updated"` } func main() { engine, err := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") if err != nil { log.Fatal(err) } err = engine.Sync2(new(User)) if err != nil { log.Fatal(err) } }
使用xorm
來操做數據庫,首先須要使用xorm.NewEngine()
建立一個引擎。該方法的參數與sql.Open()
參數相同。sql
上面代碼中,咱們演示了xorm
的一個很是實用的功能,將數據庫中的表與對應 Go 代碼中的結構體作同步。初始狀態下,數據庫test
中沒有表user
,調用Sync2()
方法會根據User
的結構自動建立一個user
表。執行後,經過describe user
查看錶結構:數據庫
若是表user
已經存在,Sync()
方法會對比User
結構與表結構的不一樣,對錶作相應的修改。咱們給User
結構添加一個Level
字段:微信
type User struct { Id int64 Name string Salt string Age int Level int Passwd string `xorm:"varchar(200)"` Created time.Time `xorm:"created"` Updated time.Time `xorm:"updated"` }
再次執行這個程序後,用describe user
命令查看錶結構:app
發現表中多了一個level
字段。工具
此修改只限於添加字段。刪除表中已有的字段會帶來比較大的風險。若是咱們User
結構的Salt
字段刪除,而後執行程序。出現下面錯誤:學習
[xorm] [warn] 2020/05/07 22:44:38.528784 Table user has column salt but struct has not related field
xorm
提供了幾個查詢和統計方法,Get/Exist/Find/Iterate/Count/Rows/Sum
。下面逐一介紹。
爲了代碼演示方便,我在user
表中插入了一些數據:
後面的代碼爲了簡單起見,忽略了錯誤處理,實際使用中不要漏掉!
Get
Get()
方法用於查詢單條數據,並使用返回的字段爲傳入的對象賦值:
type User struct { Id int64 Name string Salt string Age int Passwd string `xorm:"varchar(200)"` Created time.Time `xorm:"created"` Updated time.Time `xorm:"updated"` } func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") user1 := &User{} has, _ := engine.ID(1).Get(user1) if has { fmt.Printf("user1:%v\n", user1) } user2 := &User{} has, _ = engine.Where("name=?", "dj").Get(user2) if has { fmt.Printf("user2:%v\n", user2) } user3 := &User{Id: 5} has, _ = engine.Get(user3) if has { fmt.Printf("user3:%v\n", user3) } user4 := &User{Name: "pipi"} has, _ = engine.Get(user4) if has { fmt.Printf("user4:%v\n", user4) } }
上面演示了 3 種使用Get()
的方式:
engine.ID(1)
查詢主鍵(即id
)爲 1 的用戶;engine.Where("name=?", "dj")
查詢name = "dj"
的用戶;user3
設置了Id
字段爲 5,engine.Get(user3)
查詢id = 5
的用戶;user4
設置了字段Name
爲"pipi"
,engine.Get(user4)
查詢name = "pipi"
的用戶。運行程序:
user1:&{1 dj salt 18 12345 2020-05-08 21:12:11 +0800 CST 2020-05-08 21:12:11 +0800 CST} user2:&{1 dj salt 18 12345 2020-05-08 21:12:11 +0800 CST 2020-05-08 21:12:11 +0800 CST} user3:&{5 mxg salt 54 12345 2020-05-08 21:13:31 +0800 CST 2020-05-08 21:13:31 +0800 CST} user4:&{3 pipi salt 2 12345 2020-05-08 21:13:31 +0800 CST 2020-05-08 21:13:31 +0800 CST}
查詢條件的使用不區分調用順序,可是必須在Get()
方法以前調用。實際上後面介紹的查詢&統計方法也是如此,能夠在調用實際的方法前添加一些過濾條件。除此以外xorm
支持只返回指定的列(xorm.Cols()
)或忽略特定的列(xorm.Omit()
):
func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") user1 := &User{} engine.ID(1).Cols("id", "name", "age").Get(user1) fmt.Printf("user1:%v\n", user1) user2 := &User{Name: "pipi"} engine.Omit("created", "updated").Get(user2) fmt.Printf("user2:%v\n", user2) }
上面第一個查詢使用Cols()
方法指定只返回id
、name
、age
這 3 列,第二個查詢使用Omit()
方法忽略列created
和updated
。
另外,爲了便於排查可能出現的問題,xorm
提供了ShowSQL()
方法設置將執行的 SQL 同時在控制檯中輸出:
func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") engine.ShowSQL(true) user := &User{} engine.ID(1).Omit("created", "updated").Get(user) fmt.Printf("user:%v\n", user) }
運行程序:
[xorm] [info] 2020/05/08 21:38:29.349976 [SQL] SELECT `id`, `name`, `salt`, `age`, `passwd` FROM `user` WHERE `id`=? LIMIT 1 [1] - 4.0033ms user:&{1 dj salt 18 12345 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC}
由輸出能夠看出,執行的 SQL 語句爲:
SELECT `id`, `name`, `salt`, `age`, `passwd` FROM `user` WHERE `id`=? LIMIT 1
該語句耗時 4.003 ms。在開發中這個方法很是好用!
有時候,調試信息都輸出到控制檯並不利於咱們查詢,xorm
能夠設置日誌選項,將日誌輸出到文件中:
func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") f, err := os.Create("sql.log") if err != nil { panic(err) } engine.SetLogger(log.NewSimpleLogger(f)) engine.Logger().SetLevel(log.LOG_DEBUG) engine.ShowSQL(true) user := &User{} engine.ID(1).Omit("created", "updated").Get(user) fmt.Printf("user:%v\n", user) }
這樣xorm
就會將調試日誌輸出到sql.log
文件中。注意log.NewSimpleLogger(f)
是xorm
的子包xorm.io/xorm/log
提供的簡單日誌封裝,而非標準庫log
。
Exist
Exist()
方法查詢符合條件的記錄是否存在,它的返回與Get()
方法一致,都是(bool, error)
。不一樣之處在於Get()
會將查詢獲得的字段賦值給傳入的對象。相比之下Exist()
方法效率要高一些。若是不須要獲取數據,只要判斷是否存在建議使用Exist()
方法。
func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") user1 := &User{} has, _ := engine.ID(1).Exist(user1) if has { fmt.Println("user with id=1 exist") } else { fmt.Println("user with id=1 not exist") } user2 := &User{} has, _ = engine.Where("name=?", "dj2").Get(user2) if has { fmt.Println("user with name=dj2 exist") } else { fmt.Println("user with name=dj2 not exist") } }
Find
Get()
方法只能返回單條記錄,其生成的 SQL 語句老是有LIMIT 1
。Find()
方法返回全部符合條件的記錄。Find()
須要傳入對象切片的指針或 map 的指針:
func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") slcUsers:= make([]User, 1) engine.Where("age > ? and age < ?", 12, 30).Find(&slcUsers) fmt.Println("users whose age between [12,30]:", slcUsers) mapUsers := make(map[int64]User) engine.Where("length(name) = ?", 3).Find(&mapUsers) fmt.Println("users whose has name of length 3:", mapUsers) }
map
的鍵爲主鍵,因此若是表爲複合主鍵就不能使用這種方式了。
Iterate
與Find()
同樣,Iterate()
也是找到知足條件的全部記錄,只不過傳入了一個回調去處理每條記錄:
func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") engine.Where("age > ? and age < ?", 12, 30).Iterate(&User{}, func(i int, bean interface{}) error { fmt.Printf("user%d:%v\n", i, bean.(*User)) return nil }) }
若是回調返回一個非nil
的錯誤,後面的記錄就不會再處理了。
Count
Count()
方法統計知足條件的記錄數量:
func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") num, _ := engine.Where("age >= ?", 50).Count(&User{}) fmt.Printf("there are %d users whose age >= 50", num) }
Rows
Rows()
方法與Iterate()
相似,不過返回一個Rows
對象由咱們本身迭代,更加靈活:
func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") rows, _ := engine.Where("age > ? and age < ?", 12, 30).Rows(&User{}) defer rows.Close() u := &User{} for rows.Next() { rows.Scan(u) fmt.Println(u) } }
Rows()
的使用與database/sql
有些相似,可是rows.Scan()
方法能夠傳入一個對象,比database/sql
更方便。
Sum
xorm
提供了兩組求和的方法:
Sum/SumInt
:求某個字段的和,Sum
返回float64
,SumInt
返回int64
;Sums/SumsInt
:分別求某些字段的和,Sums
返回[]float64
,SumsInt
返回[]int64
。例如:
type Sum struct { Id int64 Money int32 Rate float32 } func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") engine.Sync2(&Sum{}) var slice []*Sum for i := 0; i < 100; i++ { slice = append(slice, &Sum{ Money: rand.Int31n(10000), Rate: rand.Float32(), }) } engine.Insert(&slice) totalMoney, _ := engine.SumInt(&Sum{}, "money") fmt.Println("total money:", totalMoney) totalRate, _ := engine.Sum(&Sum{}, "rate") fmt.Println("total rate:", totalRate) totals, _ := engine.Sums(&Sum{}, "money", "rate") fmt.Printf("total money:%f & total rate:%f", totals[0], totals[1]) }
使用engine.Insert()
方法,能夠插入單條數據,也能夠批量插入多條數據:
func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") user := &User{Name: "lzy", Age: 50} affected, _ := engine.Insert(user) fmt.Printf("%d records inserted, user.id:%d\n", affected, user.Id) users := make([]*User, 2) users[0] = &User{Name: "xhq", Age: 41} users[1] = &User{Name: "lhy", Age: 12} affected, _ = engine.Insert(&users) fmt.Printf("%d records inserted, id1:%d, id2:%d", affected, users[0].Id, users[1].Id) }
插入單條記錄傳入一個對象指針,批量插入傳入一個切片。須要注意的是,批量插入時,每一個對象的Id
字段不會被自動賦值,因此上面最後一行輸出id1
和id2
均爲 0。另外,一次Insert()
調用能夠傳入多個參數,能夠對應不一樣的表。
更新經過engine.Update()
實現,能夠傳入結構指針或map[string]interface{}
。對於傳入結構體指針的狀況,xorm
只會更新非空的字段。若是必定要更新空字段,須要使用Cols()
方法顯示指定更新的列。使用Cols()
方法指定列後,即便字段爲空也會更新:
func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") engine.ID(1).Update(&User{Name: "ldj"}) engine.ID(1).Cols("name", "age").Update(&User{Name: "dj"}) engine.Table(&User{}).ID(1).Update(map[string]interface{}{"age": 18}) }
因爲使用map[string]interface{}
類型的參數,xorm
沒法推斷表名,必須使用Table()
方法指定。第一個Update()
方法只會更新name
字段,其餘空字段不更新。第二個Update()
方法會更新name
和age
兩個字段,age
被更新爲 0。
直接調用engine.Delete()
刪除符合條件的記錄,返回刪除的條目數量:
func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") affected, _ := engine.Where("name = ?", "lzy").Delete(&User{}) fmt.Printf("%d records deleted", affected) }
若是咱們爲time.Time/int/int64
這些類型的字段設置xorm:"created"
標籤,插入數據時,該字段會自動更新爲當前時間;
若是咱們爲tiem.Time/int/int64
這些類型的字段設置xorm:"updated"
標籤,插入和更新數據時,該字段會自動更新爲當前時間;
若是咱們爲time.Time
類型的字段設置了xorm:"deleted"
標籤,刪除數據時,只是設置刪除時間,並不真正刪除記錄。
type Player struct { Id int64 Name string Age int CreatedAt time.Time `xorm:"created"` UpdatedAt time.Time `xorm:"updated"` DeletedAt time.Time `xorm:"deleted"` } func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") engine.Sync2(&Player{}) engine.Insert(&Player{Name:"dj", Age:18}) p := &Player{} engine.Where("name = ?", "dj").Get(p) fmt.Println("after insert:", p) time.Sleep(5 * time.Second) engine.Table(&Player{}).ID(p.Id).Update(map[string]interface{}{"age":30}) engine.Where("name = ?", "dj").Get(p) fmt.Println("after update:", p) time.Sleep(5 * time.Second) engine.ID(p.Id).Delete(&Player{}) engine.Where("name = ?", "dj").Unscoped().Get(p) fmt.Println("after delete:", p) }
輸出:
after insert: &{1 dj 18 2020-05-08 23:09:19 +0800 CST 2020-05-08 23:09:19 +0800 CST 0001-01-01 00:00:00 +0000 UTC} after update: &{1 dj 30 2020-05-08 23:09:19 +0800 CST 2020-05-08 23:09:24 +0800 CST 0001-01-01 00:00:00 +0000 UTC} after delete: &{1 dj 30 2020-05-08 23:09:19 +0800 CST 2020-05-08 23:09:24 +0800 CST 2020-05-08 23:09:29 +0800 CST}
建立時間一旦建立成功就不會再改變了,更新時間每次更新都會變化。已刪除的記錄必須使用Unscoped()
方法查詢,若是要真正 刪除某條記錄,也可使用Unscoped()
。
除了上面提供的方法外,xorm
還能夠執行原始的 SQL 語句:
func main() { engine, _ := xorm.NewEngine("mysql", "root:12345@/test?charset=utf8") querySql := "select * from user limit 1" reuslts, _ := engine.Query(querySql) for _, record := range reuslts { for key, val := range record { fmt.Println(key, string(val)) } } updateSql := "update `user` set name=? where id=?" res, _ := engine.Exec(updateSql, "ldj", 1) fmt.Println(res.RowsAffected()) }
Query()
方法返回[]map[string][]byte
,切片中的每一個元素都表明一條記錄,map
的鍵對應列名,[]byte
爲值。還有QueryInterface()
方法返回[]map[string]interface{}
,QueryString()
方法返回[]map[string]interface{}
。
運行程序:
salt salt age 18 passwd 12345 created 2020-05-08 21:12:11 updated 2020-05-08 22:44:58 id 1 name ldj 1 <nil>
本文對xorm
作了一個簡單的介紹,xorm
的特性遠不止於此。xorm
能夠定義結構體字段與表列名映射規則、建立索引、執行事務、導入導出 SQL 腳本等。感興趣可自行探索。好在xorm
有比較詳盡的中文文檔。
你們若是發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue😄
歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~