go web開發(gin&gorm) 之DB配置及DAO的基本使用
轉載請註明出處: https://www.cnblogs.com/funnyzpc/p/9501376.html
```
我先閑扯下,前天(也就是2018年11月16號)的某個時候,忽然有人在QQ上私聊我,一看是公司群以為是有人來慰問新人了,也沒弄清楚身份就調侃起來,就這樣:
問題是:我竟傻乎乎滴沒看出來是行政那邊的人,中午吃飯的時候和老同事聊起此事,才知道這位大鍋是人事部boss,一時間感覺事情變得搞笑起來,當然,有意思的還不止這一件,就在兩周前入職的時候,當時是復試,行政總監把車開到我之前公司樓下接我,出發到現場前給我買了杯咖啡,我說美式中杯就好了,這人說怎麽也得大杯,面試過了後,到晚上,這人又發朋友圈說他興奮的狠。。。
說實話,二當家也真夠zuo的。。。??,當然這夥計在我第一面的時候就閑聊了一個多小時,還不止,他竟然知道我小名??
```
閑聊到這兒,現在就進入本次的主題:golang web開發之Dao配置
在正式進入主題前,先說說框架的現狀,個人用的是gin-gonic框架,這是個在校大學生寫的基於go語言的高性能web框架,在此之前我對比過beego 、 iris 、gin-gonic這幾個在維護頻度和依賴支持以及star熱度方面,個人選擇了gin-gonic這個框架 ,同時也在github上選用了一套比較前衛的成型的框架代碼,東西十分的好,但是個人覺得框架集成的mysql實在是看不下去(主要是性能低了+ 穩定性不夠好+升級麻煩),遂就將數據庫換成postgresql,配置完成就開始測試Dao,需要說的是其中gorm是位臺灣胸弟寫的ORM框架,於是開始~
且先不管現有的mysql的配置,由於框架本身只集成了mysql,所以現在需要安裝一個pg的連接driver,放到指定的目錄就裝好依賴了,至於怎麽安裝,大致有二。
A>其一是使用go命令直接安裝:
1 go get -u github.com/lib/pq
B>其二是跟我一樣keng地手動安裝,就是找到github.com的源碼頁面,將整個項目以一個zip包下載下來,而後解壓到指定目錄
需要註意的是手動安裝一定要將github.com後面的路徑改成以目錄為結構的包地址。
連接組件安裝完畢開始寫一個db.go的數據庫初始化類和一個參數結構體,這裏我給出源碼:
參數結構體:
1 package config 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "regexp" 9 "strings" 10 "unicode/utf8" 11 12 "github.com/shen100/golang123/utils" 13 ) 14 15 var jsonData map[string]interface{} 16 17 func initJSON() { 18 bytes, err := ioutil.ReadFile("./config.json") 19 if err != nil { 20 fmt.Println("ReadFile: ", err.Error()) 21 os.Exit(-1) 22 } 23 24 configStr := string(bytes[:]) 25 reg := regexp.MustCompile(`/\*.*\*/`) 26 27 configStr = reg.ReplaceAllString(configStr, "") 28 bytes = []byte(configStr) 29 30 if err := json.Unmarshal(bytes, &jsonData); err != nil { 31 fmt.Println("invalid config: ", err.Error()) 32 os.Exit(-1) 33 } 34 } 35 36 type dBConfig struct { 37 Dialect string 38 Database string 39 User string 40 Password string 41 Host string 42 Port int 43 Charset string 44 URL string 45 MaxIdleConns int 46 MaxOpenConns int 47 ConnMaxLifetime int64 48 Sslmode string 49 } 50 51 // DBConfig 數據庫相關配置 52 var DBConfig dBConfig 53 54 func initDB() { 55 utils.SetStructByJSON(&DBConfig, jsonData["database"].(map[string]interface{})) 56 /* 57 mysql數據庫的連接方式 58 url := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local", 59 DBConfig.User, DBConfig.Password, DBConfig.Host, DBConfig.Port, DBConfig.Database, DBConfig.Charset) 60 */ 61 /** 62 更改mysql數據庫為postgresql 63 具體連接方式為> 64 host=myhost port=myport user=gorm dbname=gorm password=mypassword 65 */ 66 url := fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s sslmode=%s", 67 DBConfig.Host, 68 DBConfig.Port, 69 DBConfig.User, 70 DBConfig.Database, 71 DBConfig.Password, 72 DBConfig.Sslmode) 73 74 DBConfig.URL = url 75 } 76 77 type serverConfig struct { 78 APIPoweredBy string 79 SiteName string 80 Host string 81 ImgHost string 82 Env string 83 LogDir string 84 LogFile string 85 APIPrefix string 86 UploadImgDir string 87 ImgPath string 88 MaxMultipartMemory int 89 Port int 90 StatsEnabled bool 91 TokenSecret string 92 TokenMaxAge int 93 PassSalt string 94 LuosimaoVerifyURL string 95 LuosimaoAPIKey string 96 CrawlerName string 97 MailUser string //域名郵箱賬號 98 MailPass string //域名郵箱密碼 99 MailHost string //smtp郵箱域名 100 MailPort int //smtp郵箱端口 101 MailFrom string //郵件來源 102 Github string 103 BaiduPushLink string 104 } 105 106 107 func init() { 108 initJSON() 109 initDB() 110 }
連接地址一定要根據所使用的orm框架來拼接相應的連接地址才對,這算是一個坑,下面這個是gorm的官方文檔以作參考:
http://doc.gorm.io/database.html#connecting-to-a-database
db.go初始化:
1 package model 2 3 import ( 4 "fmt" 5 "os" 6 "time" 7 8 "github.com/garyburd/redigo/redis" 9 "github.com/globalsign/mgo" 10 "github.com/jinzhu/gorm" 11 _ "github.com/jinzhu/gorm/dialects/postgres" 12 "github.com/shen100/golang123/config" 13 ) 14 15 // DB 數據庫連接 16 var DB *gorm.DB 17 var ERR error 18 19 20 func initDB() { 21 DB, ERR = gorm.Open(config.DBConfig.Dialect, config.DBConfig.URL) 22 if ERR != nil { 23 fmt.Println(ERR.Error()) 24 os.Exit(-1) 25 } 26 if config.ServerConfig.Env == DevelopmentMode { 27 DB.LogMode(true) 28 } 29 DB.DB().SetMaxIdleConns(config.DBConfig.MaxIdleConns) 30 DB.DB().SetMaxOpenConns(config.DBConfig.MaxOpenConns) 31 32 /** 33 禁用表名復數> 34 !!!如不禁用則會出現表 y結尾邊ies的問題 35 !!!如果只是部分表需要使用源表名,請在實體類中聲明TableName的構造函數 36 ``` 37 func (實體名) TableName() string { 38 return "數據庫表名" 39 } 40 ``` 41 */ 42 DB.SingularTable(true) 43 //db.DB().SetConnMaxLifetime(config.DBConfig.ConnMaxLifetime) 44 } 45 46 47 func init() { 48 initDB()
49 }
這裏的初始化就是調用 gorm.Open 方法來打開db的連接,連接正常打開後設置連接池(空閑連接數、最大連接數),到這兒基本就完成了,不過,需要註意到的是:gorm默認的結構體映射是復數形式,比如你的博客表為blog,對應的結構體名就會是blogs,同時若表名為多個單詞,對應的model結構體名字必須是駝峰式,首字母也必須大寫,可能不太理解gorm的命名方式,個人也是被這個邏輯給折騰的不輕,查官方資料才知道需要配置一個參數,以實現結構體名為非復數形式:DB.SingularTable(true); 默認不設置的時候就是false;這是一坑。
好了,結構體設置完成就需要在mian.go(啟動類)中引入這兩個文件所在的package (包);像這樣:
因為個人在啟動方法中使用到這兩個包的相關方法,所以是正常引入,若是當前文件內沒有使用到,請在包的引號前加一個 "_" ,以表示自動調用相關包內的init方法(因為在main中使用過,故也會自動調用包內的init方法)。
db的基本配置已經完成了,啟動main.go若無報錯,則配置成功~
配置完成得測試下,Dao的調用,以及在結構體內配置相關映射參數,以及實現主鍵自增(很重要,裏面有坑)。
這裏本人用的是本人已經寫完的一個業務來測試,簡要的介紹下gorm的配置參數以及Dao的調用方式方法~
通過對象的方式操作數據表時,必須要有個model的結構體和數據庫表結構,這裏我給一個結構體的go代碼和表結構的截圖:
結構體:
package model import "time" // Article 文章 type Article struct { ID uint `gorm:"primary_key" sql:"auto_increment;primary_key;unique" json:"id"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` DeletedAt *time.Time `sql:"index" json:"deletedAt"` Name string `json:"name"` BrowseCount uint `json:"browseCount"` CommentCount uint `json:"commentCount"` CollectCount uint `json:"collectCount"` Status int `json:"status"` Content string `json:"content"` HTMLContent string `json:"htmlContent"` ContentType int `json:"contentType"` Categories []Category `gorm:"many2many:article_category;ForeignKey:ID;AssociationForeignKey:ID" json:"categories"` Comments []Comment `gorm:"ForeignKey:SourceID" json:"comments"` UserID uint `json:"userID"` User User `json:"user"` LastUserID uint `json:"lastUserID"` //最後一個回復話題的人 LastUser User `json:"lastUser"` LastCommentAt *time.Time `json:"lastCommentAt"` }
數據庫表結構:
由於postgresql的特殊性,在構建表的時候主鍵ID必須是serial類型才會在結構保存的時候生成一個主鍵自增的觸發器,主鍵在表結構保存後就是int類型,這是一坑(當然也只有在postgresql中存在),不論用的是oracleDB還是mySqlDB亦或是PostgreSQLDB,實現主鍵自增都需要(至少)設置一個主鍵。
再就是表結構對應的代碼結構體(Model類或實體類),配置的時候一定要註意,一定要定義字段參數標簽,標簽就目前用到的一共有三類:
gorm標簽:gorm構造標簽,這裏面可以定義字段類型、主鍵、長度、關聯關系等等,這個定義一定要有的,若字段存在多個屬性需要以key:value的形式給出,整個標簽屬性均在英文雙引號內;目前官方給出的標簽類型可以有以下幾種
sql標簽:很奇怪的是這個標簽在官方gorm裏面並沒有提到,就個人來看這個標簽可能是數據庫driver提供的,就目前用到的就只有以下幾個(自增、主鍵、唯一),若有多個屬性的時候請以分號隔開
sql:"auto_increment;primary_key;unique"
PostgreSQL的用戶需要特別註意的是:若要使用數據庫的主鍵自增,請務必聲明以上幾個屬性,否則數據插入一定會報錯!這又是一坑。。。
JSON序列化標簽: 其實,這個標簽跟ORM半毛錢關系也沒有,這裏只是提一下(因為很有用),這個標簽在對象打印或者輸出到請求端的時候可以將model的字段以別名的形式輸出,若使用默認序列化的方式將字段輸出則所有的地段都是大寫開頭,所以說十分有用~,在結構體(model)裏大概這麽定義
BrowseCount uint `json:"browseCount"`
現在就嘗試做一個保存操作,我的代碼代碼:
saveErr = model.DB.Create(&article).Error
if saveErr == nil {
if userErr := model.DB.Model(&user).Update(map[string]interface{}{
"article_count": user.ArticleCount,
"score": user.Score,
}).Error; userErr != nil {
fmt.Println(userErr.Error())
}
}
由於我的DB操作都是定義在db的配置文件裏面的一個變量
var DB *gorm.DB
所以使用的時候直接看Create方法即可(註意,保存對象一定要提前定義,使用指針的方式將對象保存)。
保存成功日誌:
[2018-11-24 22:02:03] [5.87ms] INSERT INTO "article" ("created_at","updated_at","deleted_at","name","browse_count","comment_count","collect_count","status","content","html_content","content_type","user_id","last_user_id","last_comment_at") VALUES (‘2018-11-24T22:02:03+08:00‘,‘2018-11-24T22:02:03+08:00‘,‘<nil>‘,‘怎能不說呢‘,‘0‘,‘0‘,‘0‘,‘1‘,‘欸~‘,‘‘,‘1‘,‘1‘,‘0‘,‘<nil>‘) RETURNING "article"."id"
由於go的特性,所有為空(null)字段均在記錄操作的時候以<nil>代替,介意的話可以將字段設置一個默認值,或者給表字段添加一個默認值。
在此,gorm的配置已經完成,接下來所有dao的操作均使用gorm提供Delete、Update、Insert、select等方法來實現,具體請參見官方文檔(好像有中文版):
http://doc.gorm.io/
雖然,大多數dao操作都可以通過gorm提供的api來實現,但也存在些不便的地方,主要在以下幾點:
>事務:事務是比較麻煩的一個地方,若確實需要用到事務請在第一個dao操作前調用gorm的Begin()方法,在最後一個dao操作成功後調用Commit()方法,若保存出現異常,需要在每個dao操作後做下判斷,若失敗使用
Rollback()做回退處理,坑。
>級聯查詢: 雖然官方的gorm提供級聯的方式,但在gorm標簽定義外鍵類型後並沒任何用,這裏給出的建議(比如一對多)是:在外層查詢完成後循環記錄,使用連接字段查詢出關聯記錄才可,坑。
>復雜查詢:復雜查詢需要手動寫sql(坑),由於gorm並沒有提供任何sql模板(類似於java 的 mybatis),遂,需要在代碼中手動做動態sql處理,個人建議是用大括號做模板變量,各個例子哈~
var sql = `SELECT
b.id,b.cid,b.name,b.browse_count,b.comment_count,
b.collect_count,b.created_at,b.created_by,b.updated_at, b.last_comment_at,b.last_comment_by from blog as b, blog_category as bc, blog_top as t WHERE b.cid=bc.id and b.id=t.blog_id and b.status=1 {filterByCid} ORDER BY b.created_at desc , b.updated_at desc {filterLimit}` /* 這裏當分類為所有時>取最近20條博客記錄 當分類為指定分類時>取指定分類下所有博客記錄 */ if 0== cId { sql = strings.Replace(sql, "{filterByCid}", "", -1) sql = strings.Replace(sql, "{filterLimit}", "limit 20", -1) }else{ sql = strings.Replace(sql, "{filterByCid}", "and b.cid = "+strconv.Itoa(cId), -1) sql = strings.Replace(sql, "{filterLimit}", "", -1) }
具體的調用方式是(一下代碼中的紅色部分):
if err := model.DB.Raw(sql).Scan(&blogs).Error; err != nil { SendErrJSON("error", c) return }
>分頁:gorm提供了Limit和Offset 這兩個方法來配合分頁操作,但,這裏需要說的是,在連表查詢(復雜查詢)下必須手動使用limit offset or rownum來分頁(坑),是不是很原始~
ok,本篇就到這裏就結束了,內容如有疏漏,請參閱以下文檔:
gorm文檔:
http://gorm.io/docs/
http://doc.gorm.io
gin-gonic文檔:
https://github.com/gin-gonic/gin
https://github.com/shen100/golang123
現在是:2018-11-24 23:36:28 ,各位晚安哈~
go web開發(gin&gorm) 之DB配置及DAO的基本使用