go web開發(gin&gorm) 之DB配置及DAO的基本使用

funnyZpC發表於2018-11-24

轉載請註明出處: 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 ,各位晚安哈~

 

相關文章