1. 程式人生 > 其它 >go where 不等於_Go 每日一庫之 xorm

go where 不等於_Go 每日一庫之 xorm

技術標籤:go where 不等於

以下文章來源於GoUpUp ,作者dj

簡介

Go 標準庫提供的資料庫介面database/sql比較底層,使用它來操作資料庫非常繁瑣,而且容易出錯。因而社群開源了不少第三方庫,如上一篇文章中的sqlc工具,還有各式各樣的 ORM (Object Relational Mapping,物件關係對映庫),如gorm和xorm。本文介紹xorm。xorm是一個簡單但強大的 Go 語言 ORM 庫,使用它可以大大簡化我們的資料庫操作。

快速使用

先安裝:

$gogetxorm.io/xorm

由於需要操作具體的資料庫(本文中我們使用 MySQL),需要安裝對應的驅動:

$gogetgithub.com/go-sql-driver/mysql

使用:

packagemainimport("log""time"_"github.com/go-sql-driver/mysql""xorm.io/xorm")typeUserstruct{Idint64NamestringSaltstringAgeintPasswdstring`xorm:"varchar(200)"`Createdtime.Time`xorm:"created"`Updatedtime.Time`xorm:"updated"`}funcmain(){engine,err:=xorm.NewEngine("mysql","root:
[email protected]
/test?charset=utf8")iferr!=nil{log.Fatal(err)}err=engine.Sync2(new(User))iferr!=nil{log.Fatal(err)}}

使用xorm來操作資料庫,首先需要使用xorm.NewEngine()建立一個引擎。該方法的引數與sql.Open()引數相同。

上面程式碼中,我們演示了xorm的一個非常實用的功能,將資料庫中的表與對應 Go 程式碼中的結構體做同步。初始狀態下,資料庫test中沒有表user,呼叫Sync2()方法會根據User的結構自動建立一個user表。執行後,通過describe user查看錶結構:

ac32e96cacff9c85dc3fbc87c2fe59ca.png

如果表user已經存在,Sync()方法會對比User結構與表結構的不同,對錶做相應的修改。我們給User結構新增一個Level欄位:

typeUserstruct{Idint64NamestringSaltstringAgeintLevelintPasswdstring`xorm:"varchar(200)"`Createdtime.Time`xorm:"created"`Updatedtime.Time`xorm:"updated"`}

再次執行這個程式後,用describe user命令查看錶結構:

71895be66010a95aff149bc5f4fce63e.png

發現表中多了一個level欄位。

**此修改只限於新增欄位。**刪除表中已有的欄位會帶來比較大的風險。如果我們User結構的Salt欄位刪除,然後執行程式。出現下面錯誤:

[xorm][warn]2020/05/0722:44:38.528784Tableuserhascolumnsaltbutstructhasnotrelatedfield

資料庫操作

查詢&統計

xorm提供了幾個查詢和統計方法,Get/Exist/Find/Iterate/Count/Rows/Sum。下面逐一介紹。

為了程式碼演示方便,我在user表中插入了一些資料:

f914243575fb1f806b98b73e4e9745ca.png

後面的程式碼為了簡單起見,忽略了錯誤處理,實際使用中不要漏掉!

Get

Get()方法用於查詢單條資料,並使用返回的欄位為傳入的物件賦值:

typeUserstruct{Idint64NamestringSaltstringAgeintPasswdstring`xorm:"varchar(200)"`Createdtime.Time`xorm:"created"`Updatedtime.Time`xorm:"updated"`}funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")user1:=&User{}has,_:=engine.ID(1).Get(user1)ifhas{fmt.Printf("user1:%v",user1)}user2:=&User{}has,_=engine.Where("name=?","dj").Get(user2)ifhas{fmt.Printf("user2:%v",user2)}user3:=&User{Id:5}has,_=engine.Get(user3)ifhas{fmt.Printf("user3:%v",user3)}user4:=&User{Name:"pipi"}has,_=engine.Get(user4)ifhas{fmt.Printf("user4:%v",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:&{1djsalt18123452020-05-0821:12:11+0800CST2020-05-0821:12:11+0800CST}user2:&{1djsalt18123452020-05-0821:12:11+0800CST2020-05-0821:12:11+0800CST}user3:&{5mxgsalt54123452020-05-0821:13:31+0800CST2020-05-0821:13:31+0800CST}user4:&{3pipisalt2123452020-05-0821:13:31+0800CST2020-05-0821:13:31+0800CST}

查詢條件的使用不區分呼叫順序,但是必須在Get()方法之前呼叫。實際上後面介紹的查詢&統計方法也是如此,可以在呼叫實際的方法前新增一些過濾條件。除此之外xorm支援只返回指定的列(xorm.Cols())或忽略特定的列(xorm.Omit()):

funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")user1:=&User{}engine.ID(1).Cols("id","name","age").Get(user1)fmt.Printf("user1:%v",user1)user2:=&User{Name:"pipi"}engine.Omit("created","updated").Get(user2)fmt.Printf("user2:%v",user2)}

上面第一個查詢使用Cols()方法指定只返回id、name、age這 3 列,第二個查詢使用Omit()方法忽略列created和updated。

另外,為了便於排查可能出現的問題,xorm提供了ShowSQL()方法設定將執行的 SQL 同時在控制檯中輸出:

funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")engine.ShowSQL(true)user:=&User{}engine.ID(1).Omit("created","updated").Get(user)fmt.Printf("user:%v",user)}

執行程式:

[xorm][info]2020/05/0821:38:29.349976[SQL]SELECT`id`,`name`,`salt`,`age`,`passwd`FROM`user`WHERE`id`=?LIMIT1[1]-4.0033msuser:&{1djsalt18123450001-01-0100:00:00+0000UTC0001-01-0100:00:00+0000UTC}

由輸出可以看出,執行的 SQL 語句為:

SELECT`id`,`name`,`salt`,`age`,`passwd`FROM`user`WHERE`id`=?LIMIT1

該語句耗時 4.003 ms。在開發中這個方法非常好用!

有時候,除錯資訊都輸出到控制檯並不利於我們查詢,xorm可以設定日誌選項,將日誌輸出到檔案中:

funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")f,err:=os.Create("sql.log")iferr!=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",user)}

這樣xorm就會將除錯日誌輸出到sql.log檔案中。注意log.NewSimpleLogger(f)是xorm的子包xorm.io/xorm/log提供的簡單日誌封裝,而非標準庫log。

Exist

Exist()方法查詢符合條件的記錄是否存在,它的返回與Get()方法一致,都是(bool, error)。不同之處在於Get()會將查詢得到的欄位賦值給傳入的物件。相比之下Exist()方法效率要高一些。如果不需要獲取資料,只要判斷是否存在建議使用Exist()方法。

funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")user1:=&User{}has,_:=engine.ID(1).Exist(user1)ifhas{fmt.Println("userwithid=1exist")}else{fmt.Println("userwithid=1notexist")}user2:=&User{}has,_=engine.Where("name=?","dj2").Get(user2)ifhas{fmt.Println("userwithname=dj2exist")}else{fmt.Println("userwithname=dj2notexist")}}

Find

Get()方法只能返回單條記錄,其生成的 SQL 語句總是有LIMIT 1。Find()方法返回所有符合條件的記錄。Find()需要傳入物件切片的指標或 map 的指標:

funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")slcUsers:=make([]User,1)engine.Where("age>?andage

map的鍵為主鍵,所以如果表為複合主鍵就不能使用這種方式了。

Iterate

與Find()一樣,Iterate()也是找到滿足條件的所有記錄,只不過傳入了一個回撥去處理每條記錄:

funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")engine.Where("age>?andage

如果回撥返回一個非nil的錯誤,後面的記錄就不會再處理了。

Count

Count()方法統計滿足條件的記錄數量:

funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")num,_:=engine.Where("age>=?",50).Count(&User{})fmt.Printf("thereare%duserswhoseage>=50",num)}

Rows

Rows()方法與Iterate()類似,不過返回一個Rows物件由我們自己迭代,更加靈活:

funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")rows,_:=engine.Where("age>?andage

Rows()的使用與database/sql有些類似,但是rows.Scan()方法可以傳入一個物件,比database/sql更方便。

Sum

xorm提供了兩組求和的方法:

  • Sum/SumInt:求某個欄位的和,Sum返回float64,SumInt返回int64;
  • Sums/SumsInt:分別求某些欄位的和,Sums返回[]float64,SumsInt返回[]int64。

例如:

typeSumstruct{Idint64Moneyint32Ratefloat32}funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")engine.Sync2(&Sum{})varslice[]*Sumfori:=0;i

插入

使用engine.Insert()方法,可以插入單條資料,也可以批量插入多條資料:

funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")user:=&User{Name:"lzy",Age:50}affected,_:=engine.Insert(user)fmt.Printf("%drecordsinserted,user.id:%d",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("%drecordsinserted,id1:%d,id2:%d",affected,users[0].Id,users[1].Id)}

插入單條記錄傳入一個物件指標,批量插入傳入一個切片。需要注意的是,批量插入時,每個物件的Id欄位不會被自動賦值,所以上面最後一行輸出id1和id2均為 0。另外,一次Insert()呼叫可以傳入多個引數,可以對應不同的表。

更新

更新通過engine.Update()實現,可以傳入結構指標或map[string]interface{}。對於傳入結構體指標的情況,xorm只會更新非空的欄位。如果一定要更新空欄位,需要使用Cols()方法顯示指定更新的列。使用Cols()方法指定列後,即使欄位為空也會更新:

funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/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()刪除符合條件的記錄,返回刪除的條目數量:

funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")affected,_:=engine.Where("name=?","lzy").Delete(&User{})fmt.Printf("%drecordsdeleted",affected)}

建立時間、更新時間、軟刪除

如果我們為time.Time/int/int64這些型別的欄位設定xorm:"created"標籤,插入資料時,該欄位會自動更新為當前時間;

如果我們為tiem.Time/int/int64這些型別的欄位設定xorm:"updated"標籤,插入和更新資料時,該欄位會自動更新為當前時間;

如果我們為time.Time型別的欄位設定了xorm:"deleted"標籤,刪除資料時,只是設定刪除時間,並不真正刪除記錄。

typePlayerstruct{Idint64NamestringAgeintCreatedAttime.Time`xorm:"created"`UpdatedAttime.Time`xorm:"updated"`DeletedAttime.Time`xorm:"deleted"`}funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")engine.Sync2(&Player{})engine.Insert(&Player{Name:"dj",Age:18})p:=&Player{}engine.Where("name=?","dj").Get(p)fmt.Println("afterinsert:",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("afterupdate:",p)time.Sleep(5*time.Second)engine.ID(p.Id).Delete(&Player{})engine.Where("name=?","dj").Unscoped().Get(p)fmt.Println("afterdelete:",p)}

輸出:

afterinsert:&{1dj182020-05-0823:09:19+0800CST2020-05-0823:09:19+0800CST0001-01-0100:00:00+0000UTC}afterupdate:&{1dj302020-05-0823:09:19+0800CST2020-05-0823:09:24+0800CST0001-01-0100:00:00+0000UTC}afterdelete:&{1dj302020-05-0823:09:19+0800CST2020-05-0823:09:24+0800CST2020-05-0823:09:29+0800CST}

建立時間一旦建立成功就不會再改變了,更新時間每次更新都會變化。已刪除的記錄必須使用Unscoped()方法查詢,如果要真正 刪除某條記錄,也可以使用Unscoped()。

執行原始的 SQL

除了上面提供的方法外,xorm還可以執行原始的 SQL 語句:

funcmain(){engine,_:=xorm.NewEngine("mysql","root:[email protected]/test?charset=utf8")querySql:="select*fromuserlimit1"reuslts,_:=engine.Query(querySql)for_,record:=rangereuslts{forkey,val:=rangerecord{fmt.Println(key,string(val))}}updateSql:="update`user`setname=?whereid=?"res,_:=engine.Exec(updateSql,"ldj",1)fmt.Println(res.RowsAffected())}

Query()方法返回[]map[string][]byte,切片中的每個元素都代表一條記錄,map的鍵對應列名,[]byte為值。還有QueryInterface()方法返回[]map[string]interface{},QueryString()方法返回[]map[string]interface{}。

執行程式:

saltsaltage18passwd12345created2020-05-0821:12:11updated2020-05-0822:44:58id1nameldj1

總結

本文對xorm做了一個簡單的介紹,xorm的特性遠不止於此。xorm可以定義結構體欄位與表列名對映規則、建立索引、執行事務、匯入匯出 SQL 指令碼等。感興趣可自行探索。好在xorm有比較詳盡的中文文件。

大家如果發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue

參考

  1. xorm GitHub:https://github.com/go-xorm/xorm
  2. xorm 手冊:http://gobook.io/read/gitea.com/xorm/manual-zh-CN/
  3. Go 每日一庫 GitHub:https://github.com/darjun/go-daily-lib