【Go開源寶藏】GORM 專場 (含思維導圖) | 持續更新

語言: CN / TW / HK

theme: cyanosis

本文已參與 「掘力星計劃」 ,贏取創作大禮包,挑戰創作激勵金

寫在前面

本人只是一個Go語言的初學者,這篇文只是把我平常經常用到的都總結起來而已。 具體詳細的內容建議到去GORM的中文文件檢視。 當然這篇文章也會持續更新,記錄我的CURD打磨過程 這篇文章也會持續更新噠

思維導圖

請新增圖片描述

@TOC

在這裡插入圖片描述

1. 簡介

  • 介紹:一個Go語言開發的ORM庫,是位元組的一位大牛寫噠!!國產框架!
  • 下載:go get -u github.com/jinzhu/gorm

流程: 1. 匯入Gorm 2. 匯入資料庫驅動 3. 定義資料庫模型 4. 與資料庫連線 5. 欄位遷移對映 6. 增、刪、改、查 7. 關閉資料庫連線

2. 資料庫驅動

| 資料庫 | 驅動 | 匯入 |
|--|--|-- | mysql | github.com/go-sql-driver/mysql | github.com/jinzhu/gorm/dialects/mysql |
| postgresql | github.com/lib/pq | github.com/jinzhu/gorm/dialects/postgres |sqlite | github.com/mattn/go-sqlite3 | github.com/jinzhu/gorm/dialects/sqlite |sqlserver | | github.com/jinzhu/gorm/dialects/mssql

3. 連線(MySQL為例子)

配置ini檔案 ini [mysql] Db = mysql DbHost = 127.0.0.1 DbPort = 3306 DbUser = root DbPassWord = root DbName = carsys

讀取配置檔案 go file, err := ini.Load("./conf/config.ini") if err != nil { fmt.Println("配置檔案讀取錯誤,請檢查檔案路徑:", err) } LoadMysqlData(file)

資料庫路徑拼接

go path := strings.Join([]string{DbUser, ":", DbPassWord, "@tcp(", DbHost, ":", DbPort, ")/", DbName, "?charset=utf8&parseTime=true"}, "") // 路徑拼接 model.Database(path) // 連線資料庫

連線資料庫,這個是放在了model包,所以後面的操作都是model.DB

```go var DB *gorm.DB //全域性的資料庫入口

func Database(connString string) { db, err := gorm.Open("mysql", connString) db.LogMode(true) if err != nil { panic(err) } if gin.Mode() == "release" { db.LogMode(false) } db.SingularTable(true) //預設不加複數s,不然他的每個資料後面都會加上和s比如user變成users db.DB().SetMaxIdleConns(20) //設定連線池,空閒 db.DB().SetMaxOpenConns(100) //設定開啟最大連線 db.DB().SetConnMaxLifetime(time.Second * 30) DB = db migration() //模型遷移對映到資料庫 } ```

4. 遷移(MySQL為例子)

自動遷移資料庫到最新版本,最會新增列和索引,不會修改型別刪除列

4.1 構建資料庫表

這個gorm.Model是自動加上create_timeupdate_timedelete_time

檢視原始碼裡面得gorm中得model在這裡插入圖片描述

然後我們可以像這樣直接去定義一個struct模型,準備對映到資料庫中,就不用寫sql語句了。 後面可以加上tag就像是gorm:"unique"這樣的,表示這個是唯一的。

```go import "github.com/jinzhu/gorm"

type User struct { gorm.Model UserName string gorm:"unique" Email string PasswordDigest string Nickname string gorm:"unique" Status string Limit int // 0 非管理員 1 管理員 Type int // 0表示使用者 1表示商家 Avatar string gorm:"size:1000" Monery int } ```

4.2 資料庫模型遷移

gorm提供了一個AutoMigrate的方法,很方便地進行對映遷移的工作。 go func migration() { //自動遷移模式 DB.Set("gorm:table_options", "charset=utf8mb4"). AutoMigrate(&User{}) }

4.3 tags束縛

4.3.1 一對多

語義場景:評論和帖子的關係。一個帖子有很多的評論,但是這個評論只能在這個帖子下面。

所以可以通過foreignkey進行外來鍵的束縛。外來鍵到Social這個模型。關係標識為social_id

go type Comment struct { gorm.Model Content string ParentId uint // 父評論ID UserID uint // 使用者ID ReplyName string // 回覆者名字 UserName string UserAvatar string SocialId uint `gorm:"foreignkey:Social;association_jointable_foreignkey:social_id"` //社群帖子ID Children []Comment `gorm:"foreignkey:ParentId"` }

4.3.2 多對多

語義環境:一個使用者可以有多個好友,反過來也可以。所以就是user表自己的多對多。

使用tags是many2many

go type User struct { gorm.Model Relations []User `gorm:"many2many:relation; association_jointable_foreignkey:relation_id"` UserName string Email string //`gorm:"unique"` Avatar string `gorm:"size:1000"` Phone string BanTime int OpenID string `gorm:"unique"` } 這樣的多對多的關聯,就會多建立一個表來存兩者的關係。


5. 增

5.1 一對一

create函式進行建立。

go category := model.Category{ CategoryName: service.CategoryName, EnglishName:service.EnglishName, } err := model.DB.Create(&category).Error

5.2 一對多

語義環境:這裡就是一評論表,socialId就是帖子, 一個帖子對應多個評論,和一對一是 一樣的建立方式

go var comment model.Comment comment = model.Comment{ UserID: user.ID, Content: service.Content, ParentId: service.ParentId, UserName: user.UserName, UserAvatar: user.Avatar, SocialId: service.SocialID, } err := model.DB.Create(&comment).Error

5.3 多對多

語義環境:使用者表,使用者和使用者之間建立多對多聯絡

go type User struct { gorm.Model Relations []User `gorm:"many2many:relation; association_jointable_foreignkey:relation_id"` UserName string Email string //`gorm:"unique"` Avatar string `gorm:"size:1000"` Phone string BanTime int OpenID string `gorm:"unique"` }

選出user表中的兩個元素。然後進行關聯繫結。 go var user model.User var friend model.User model.DB.Model(&friend).Where(`id = ? `,id).First(&friend) //被關注者 model.DB.Model(&user).Where(`id = ?`,userId).First(&user) //關注者 err := model.DB.Model(&user).Association(`Relations`).Append([]model.User{friend}).Error

這條語句就是把這個user和這個friend建立聯絡,存放在Relations表裡面。


6. 刪

6.1 一對一

GORM中的delete刪除是軟刪除,就是雖然是刪除了,但是並沒有完全刪除,而是保留在資料中但是查詢並不查詢這條已經刪除的語句。

  • 單記錄刪除

先找到這條資料

go var social model.Society code := e.Success err := model.DB.First(&social, id).Error // 根據id找到這個social,記得傳地址。

然後進行刪除即可

go err = model.DB.Delete(&social).Error

  • 批量刪除

go db.Where("user_name like ? ","%Fan%").Delete(User{})

  • 永久刪除:在資料庫直接清空了,不是軟刪除了 go db.Unscoped().Where("name Like ?","%6%").Delete(User{})

6.2 一對多

其實一對一就是特殊的一對多

刪除和一對一其實是一樣的操作。

go var social model.Society model.DB.First(&social, id) // 找到這個帖子 model.DB.Delete(&social) // 將其進行刪除

6.3 多對多

找到這兩者的關係,然後進行關聯刪除即可。就會從Relations表中把這兩者的關係進行刪除。 go var user model.User var friend []model.User model.DB.Model(&friend).Where(`id = ?`,id).First(&friend) //被關注者 model.DB.Model(&user).Where(`id = ?`,userId).First(&user) //關注者 err := model.DB.Model(&user).Association(`Relations`).Delete(friend).Error


7. 改

7.1 一對一

  • Save 會儲存所有的欄位,即使欄位是零值 go social := model.Society{ CategoryID: service.CategoryID, Title: service.Title, Content: service.Content, Picture: service.Picture, CategoryName: service.CategoryName, } err := model.DB.Save(&social).Error

  • Update 當使用Update更新單個列時,需要指定條件,否則會返回ErrMissingWhereClause錯誤。 go model.DB.Model(model.User{}).Where("open_id=?", openid).Update("phone", phone)

  • Updates

    批量更新,只會更新指定欄位,但是如果是空值的情況下是不更新的成空值的。

只更新指定欄位 go model.DB.Table("user").Where("user_name = ?","FanOne"). Updates(map[string]interface{}{"phone":"10001","email":"[email protected]"}) 只更新更改的非零值的欄位 go model.DB.Model(&User{}).Where("user_name = ?","FanOne"). Updates(User{Phone:"10001",Email:"[email protected]"})

7.2 一對多

直接更新欄位,同上。

7.3 多對多

由於多對多的是根據關係來關聯的,所以更新欄位關係是無關的,同上。


8. 查

  • First 是找到第一個
  • Last 是找到最後一個
  • Find 是找到全部

8.1 一對一

語義環境:使用者表 go type User struct { gorm.Model Relations []User `gorm:"many2many:relation; association_jointable_foreignkey:relation_id"` UserName string Email string //`gorm:"unique"` Avatar string `gorm:"size:1000"` Phone string BanTime int OpenID string `gorm:"unique"` }

8.1.1 基本查詢

  • 查詢第一個 go var u1 User db.First(&u1) fmt.Println(u1)

  • 查詢最後一個 go var u2 User db.Last(&u2) fmt.Println(u2)

  • 按主鍵獲取 go var u3 User db.First(&u3,2) //不指定的話,預設主鍵是id fmt.Println(u3)

  • 按條件查詢一條 go var u4 User db.First(&u4,"user_name=?","FanOne") //不指定的話,預設主鍵是id fmt.Println(u4)

  • 獲取所有資料 go var u5 []User db.Find(&u5) //查詢所有的資料資訊 fmt.Println(u5)

8.1.2 Where條件

  • 單一條件 go var u1 User db.Where("user_name = ?","FanOne").First(&u1)

  • 巢狀查詢 go var u1s []User db.Where("user_name=?","fanone").Where("phone=?","10086").Find(&u1s)

  • 多條件

go var u2 User db.Where("user_name = ? AND phone = ?","FanOne","10086").First(&u2) - 模糊查詢 記得加上% go var u3 User db.Where("user_name LIKE ?","%Fan%").First(&u3) //返回第一個user_name與Fan相近的資料 var u3s []User db.Where("user_name LIKE ?","%Fan%").Find(&u3) //返回多個user_name與Fan相近的資料 - 範圍查詢 go var u4 []User db.Where("id > ?","FanOne","10").Find(&u2) // 找到id>10那部分的資料 - 陣列形式 go var u5 []User db.Where("user_name in (?)",[]string{"Fan","One"}).Find(&u5) - 結構體形式 go var u6 User db.Where(&User{UserName:"FanOne"}).First(&u6) - map形式 go var u7 User db.Where(map[string]interface{}{"user_name":"fan","phone":"10086"}).First(&u7)

8.1.3 Not條件

  • 查詢UserName != FanOne的資料,返回列表 go var u1 []User db.Not(&User{UserName:"FanOne"}).Find(&u1)
  • 查詢UserName != FanOne的資料,返回第一條資料 go var u2 User db.Not("user_name","FanOne").First(&u2)

  • 查詢UserName != fan的資料,返回第一條資料

go var u3 User db.Not("user_name = ?","fan").First(&u2)

  • 查詢 UserName 不在這個字串列表當中的資料,返回第一條資料

go var u4 User db.Not("user_name",[]string{"fan","one"}).First(&u4)

  • 查詢 UserName 不在這個字串列表當中的資料,返回列表

go var u5 []User db.Not("user_name in (?)",[]string{"fan","one"}).Find(&u5)

8.1.4 選擇查詢

  • 只查詢user_name這一列的資料 go var u1 []User db.Select("user_name").Find(&u1)
  • 只查詢user_name和phone這兩列的資料,字串陣列形式 go var u1 []User db.Select([]string{"user_name","phone"}).Find(&u1)
  • 只查詢user_name和phone這兩列的資料,字串陣列形式 go var u1 []User db.Select("user_name , phone").Find(&u1)

8.1.5 排序

預設排序是升序的 go var u1s,u2s []User db.Order("name desc").Find(&u1s) //按照名字降序 db.Order("name asc",true).Find(&u1s) //按照名字升序

8.1.6 分頁

Limit 頁面大小 Offset 頁數 go var us []User db.Order("id asc").Offset(5).Limit(3).Find(&us)

8.1.7 數量

go var count int db.Model(&User{}).Count(&count)

go var count int db.Model(&User{}).Where("user_name Like ?","%6%").Count(&count)

go var count int db.Table("users").Where("user_name Like ?","%6%").Count(&count)

8.1.8 原生SQL

當我們要進行比較複雜的查詢的時候,可以通過這個原生sql進行查詢。進行並查詢操作

``go query := "SELECT * FROMwork_timeWHEREwork_time.deleted_atIS NULL AND month = "+"+ data.Month +"`

if data.Search!="" {
    query += ` AND (( mask LIKE "`+"%"+data.Search +"%"+ `" ) OR ( number LIKE "`+"%"+data.Search+"%"+ `" ))`
}
if data.DepartmentName!="" {
    query += " AND department_name LIKE " + `"`+"%" +data.DepartmentName+"%" + `"`
}
if data.WorkType!="" {
    query += " AND work_type LIKE " + `"`+"%" +data.WorkType+"%" + `"`
}
if data.EmployeeStatus!="" {
    query += " AND employee_status LIKE " + `"`+"%" +data.EmployeeStatus+"%" + `"`
}
if data.EmployeeType!="" {
    query += " AND employee_type LIKE " + `"`+"%" +data.EmployeeType+"%" + `"`
}
if data.Status!="" {
    query += " AND status LIKE " + `"`+"%" +data.Status+"%" + `"`
}
if err := db.Offset((data.PageNum - 1) * data.PageSize).Limit(data.PageSize).Raw(query).Scan(&workTimeList).Error; err != nil {
    code = e.LEVEL_ERROR
    return serializer.Response{
        Level: code,
        Data:  e.GetMsg(code),
        Error: err.Error(),
    }
}

```

8.2 一對多

語義場景 Comment是評論表,Social是帖子表 一個帖子對應多個評論,所以在多端進行關聯

go type Comment struct { gorm.Model Content string ParentId uint // 父評論ID UserID uint // 使用者ID ReplyName string // 回覆者名字 UserName string UserAvatar string SocialId uint `gorm:"ForeignKey:Social;AssociationForeignKey:social_id"` //社群帖子ID Children []Comment `gorm:"ForeignKey:Comment;AssociationForeignKey:comment_id"` }

  • 查單個用Related關聯

go var comment model.Comment model.DB.Model(&comment). Related(&comment.SocialId , "social_id").Find(&comment)

  • 查多個用Preload預載入 go var comment []model.Comment model.DB.Model(&comment).Preload("Children").Find(&comment)

8.3 多對多

語義場景 使用者自己對自己多對多。類似好友。一個人可以有多個好友,一個好友又可以包括其他好友。

go type User struct { gorm.Model Relations []User `gorm:"many2many:relation; association_jointable_foreignkey:relation_id"` UserName string Email string //`gorm:"unique"` Avatar string `gorm:"size:1000"` Phone string BanTime int OpenID string `gorm:"unique"` }

user表中的多對多查詢,可以通過這個Association進行聯合查詢查詢與之相關的資料資訊。

go var user model.User var friendsList []model.User model.DB.Table("user").Where("id = ?",id).First(&user) err := model.DB.Model(&user).Association("Relations"). Limit(service.PageSize).Offset((service.PageNum-1)*service.PageSize). Find(&friendsList).Error


9. 錯誤總結

錯誤 1:Error 1103: Incorrect table name

不知道是哪個表 go var employees []model.Employee var count int db := model.DB 改成 go var employees []model.Employee var count int db := model.DB.Model(model.Employee{})

錯誤2:sql: no rows in result set

注意: Count()查詢必須在where條件之後,limit,offset分頁之前!

如果寫在limit,offset 分頁 之前之後,在第二頁開始就會報錯 sql: no rows in result set

count也不能太前,否則查詢出來的總數將是所有資料總數,非條件過濾後的條數

go db.Limit(data.PageSize).Offset((data.PageNum-1)*data.PageSize).Find(&employees).Count(&count) 改成 go db.Count(&count).Limit(data.PageSize).Offset((data.PageNum-1)*data.PageSize).Find(&employees)