gorm整理


1. 約定

  • GORM 默認會使用結構體中名為ID的字段作為表的主鍵
  • GORM 結構體的字段以蛇形小寫的形式對應到數據庫的字段名
  • GORM 時間跟蹤, 會自動更新表的created_at updated_at列(對應結構體字段CreatedAt和UpdatedAt, 類型time.Time)
  • GORM 軟刪除是指表的deleted_at有值

2. 結構體標簽

  • 類型定義,索引: gorm:"type:varchar(100);index" gorm:"type:decimal(7,2)"
  • 自定義列名: gorm:"column:name"
  • 指定string長度: gorm:"size:64"
  • 指定主鍵: gorm:"primary_key"
  • 唯一約束: gorm:"unique"
  • 默認值: gorm:"default:-1" gorm:"default:'abc123'" 字符串默認值使用單引號或\轉義的雙引號包裹
  • 索引: gorm:"index:idx_user" idx_user是索引名,多個索引同名則是聯合索引
  • 唯一索引: 同索引, gorm:"unique_index"
  • 其他: not null 非空, embedded 嵌入(就是結構體同級別), precision列精度,embedded_prefix 設置嵌入結構的前綴

3. 創建記錄

u := User{Name: "Jinzhu", Age: 18}  
if db.NewRecord(u) {   //=> 判斷主鍵是否存在, 主鍵為空返回`true`   db.NewRecord不會取查數據庫,只是檢查結構體的主鍵字段是否非空     
        db.Create(&u)  //創建后, u的結構體會被數據庫實際的值填充,比如u.Id已經有值了
}
  • 創建前預處理Hook, 只需要寫BeforeCreate方法. 例如:

    func (user *User) BeforeCreate(scope *gorm.Scope) error {
        scope.SetColumn("ID", uuid.New())
        return nil
    } 
    
  • 擴展SQL選項(這里不介紹,給例子):

// 為Instert語句添加擴展SQL選項
db.Set("gorm:insert_option", "ON CONFLICT").Create(&product)
// INSERT INTO products (name, code) VALUES ("name", "code") ON CONFLICT;

4. 更新

  • 單字段:
db.Model(&user).Update("name", "hello")
  • 多字段
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})  //map
db.Model(&user).Updates(User{Name: "hello", Age: 18}) //struct,更新非零值字段
  • 只更新選擇的字段
//白名單形式: 使用Select("name"),這樣就只更新name,其他字段忽略
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

//黑名單形式: 使用Omit("name")來避免更新name字段
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
// UPDATE users SET age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
  • 純凈版更新: 避開BeforeUpdate / AfterUpdate鈎子方法和關聯影響
用UpdateColumn, UpdateColumns替代Update和Updates
  • 批更新(鈎子函數不運行,好像不是!!!)
db.Table("users").Where("id IN (?)", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);

// Update with struct only works with none zero values, or use map[string]interface{}
//批更新,我的理解就是多個更新,所以下面用空的User結構體
db.Model(User{}).Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18;

// Get updated records count with `RowsAffected`
db.Model(User{}).Updates(User{Name: "hello", Age: 18}).RowsAffected
  • SQL表達式
DB.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
// UPDATE "products" SET "price" = price * '2' + '100', "updated_at" = '2013-11-17 21:34:10' WHERE "id" = '2';

DB.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
// UPDATE "products" SET "price" = price * '2' + '100', "updated_at" = '2013-11-17 21:34:10' WHERE "id" = '2';

DB.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = '2';

DB.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = '2' AND quantity > 1;
  • 鈎子
//BeforeUpdate, BeforeSave, 使用scope.SetColumn來修改字段
func (u *user) BeforeUpdate(scope *gorm.Scope) (err error) {
	scope.SetColumn("CreateAt", time.Now().Unix()) 
	return nil
}

5.刪除

  • 使用Model刪
// 注意: gorm會根據主鍵來刪, 參數的主鍵必須有值, 否則會清空所有記錄!!!
db.Delete(&email)

//批刪除
db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})
db.Delete(Email{}, "email LIKE ?", "%jinzhu%")
  • 軟刪除(就是標記DeleteAt,不實際刪除, 這里略去)

6. 查詢

  • 簡單查詢:
db.First(&user)  //通過主鍵查詢第一條記錄,user變量的主鍵必須有值
db.First(&user, 10) // 查詢指定的某條記錄(只可在主鍵為整數型時使用)
db.First(&user, User{Name:"tom",Age:13})   // 結構體作為內聯條件
db.First(&user, map[string]interface{}{"Name":"tom"})   // map作為內聯條件
db.Last(&user)  
db.Take(&user)   //隨機,// SELECT * FROM users LIMIT 1;
db.Find(&users)  //取所有記錄,變量users是切片
// *** 注意: 不管是First還是Find,如果傳入的是切片,就不會報ErrRecordNotFound錯誤;如果傳入的是結構體,則有可能報ErrRecordNotFound錯誤
  • WHERE查詢:
db.Where("name = ?", "jinzhu").First(&user) 
//where()參數的形式:
//     字符串(可能有問號); 例如: 
//          "id <> ?",3 
//          "id IN (?)",ids
//          "name LIKE ?","%tom%" 
//          "id > ? AND state = 1", 100
//     結構體和映射:
//          &User{Name: "jinzhu", Age: 20}  //注意,零值字段不參與條件的構建
//          map[string]interface{}{"name": "jinzhu", "age": 20}
  • NOT:
db.Not("name", "jinzhu").First(&user)  //單條件,
name不為"jinzhu",條件可以寫成db.Not("name = ?", "jinzhu")
db.Not(User{Name: "jinzhu"}).First(&user)  //單條件,結構體
db.Not("name", []string{"jinzhu", "jinzhu 2"}).Find(&users) //多條件,對應SQL的NOT IN
db.Not([]int64{1,2,3}).First(&user)  //多條件,NOT IN 主鍵
  • OR:
 //Or()參數可以寫成:
 // 文本形式:  "role = ?", "super_admin"
 // 結構體形式: User{Name: "jinzhu 2"}
 db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2"}).Find(&users)
 // 映射形式: map[string]interface{}{"name": "jinzhu 2"}
 db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2"}).Find(&users)
  • inline條件:
db.First(&user, 23)  //僅限主鍵是整型時
db.Find(&user, "name = ?", "jinzhu")  //注意事項參考"多個立即執行方法" 
db.Find(&users, User{Age: 20})  //結構體條件形式
db.Find(&users, map[string]interface{}{"age": 20})  //map條件形式
  • 多個“立即執行方法”串聯(Multiple Immediate Methods)時的注意事項
// “立即執行方法”:基本就是和CRUD相關的,例如Find(),Update()等,非立即執行方法就是類似Where(),Order()之類的
//多個立即執行方法串聯,比如Find(&ul).Count(&cnt)注意事項:這種情況,后者(這里是Count)會使用前者(這里是Find)的查詢條件(例如Find前的Where)
//
//  !!!  忽略inline條件 !!!
//例如下面前面的Find中的inline條件被后面的Count()忽略了:
db.Where("name LIKE ?", "jinzhu%").Find(&users, "id IN (?)", []int{1, 2, 3}).Count(&count)
//會生成:
//    SELECT * FROM users WHERE name LIKE 'jinzhu%' AND id IN (1, 2, 3)
//    SELECT count(*) FROM users WHERE name LIKE 'jinzhu%'
  • FirstOrCreate
//FirstOrCreate是按照條件找到第一條記錄,如果找不到,則使用給定條件創建一條記錄。條件只支持結構體和映射
//下面舉得例子是使用User結構體:
//     type User struct {
//         ID   uint32
//         Age  uint32
//         Name string
//         Job  uint32
//     }

// 無則創建
db.FirstOrCreate(&user, User{Age:3, Name: "tomzzz"})
// INSERT INTO "users" (age,name) VALUES (3,"tomzzz");
// user變量的值從User結構體零值變成User{Id: 112, Age:3, Name: "tomzzz", Job:""}   

// 有則填充結構體變量
db.Where(User{Name: "tomzzz"}).FirstOrCreate(&user)
//user變量的值從User結構體零值變成User{Id: 112, Age:3, Name: "tomzzz", Job:""}   

// 使用Attrs()
// 未找到
db.Where(User{Age:20, Name: "tomyyy"}).Attrs(User{Job: "teacher"}).FirstOrCreate(&user)
// SELECT * FROM users WHERE age = 20 AND name = 'tomyyy';
// INSERT INTO "users" (age, name, job) VALUES (20,"tomyyy", "teacher");
// user變量的值從User結構體零值變成User{Id: 113, Age:20, Name: "tomyyy", Job:"teacher"}  

// 找到,則使用數據庫值填充結構體
db.Where(User{Age:20, Name: "tomyyy"}).Attrs(User{Job: "nurse"}).FirstOrCreate(&user)
// SELECT * FROM users WHERE age = 20 AND name = 'tomyyy';
// user變量的值從User結構體零值變成User{Id: 113, Age:20, Name: "tomyyy", Job:"teacher"} , 注意user變量的Job字段,使用了數據庫記錄的值

//使用Assign()
// 未找到
db.Where(User{Age:22, Name: "tomxxx"}).Assign(User{Job: "lawyer"}).FirstOrCreate(&user)
// SELECT * FROM users WHERE age = 22 AND name = 'tomxxx';
//INSERT INTO "users" (age, name, job) VALUES (22,"tomxxx", "lawyer");
// user變量的值從User結構體零值變成User{Id: 114, Age:22, Name: "tomxxx", Job:"lawyer"} 

// 找到, 使用Assign指定的值寫入數據庫
db.Where(User{Age:22, Name: "tomxxx"}).Assign(User{Job: "policeman"}).FirstOrCreate(&user)
// SELECT * FROM users WHERE age = 22 AND name = 'tomxxx';
// UPDATE users SET job = 'policeman'  WHERE id = 5 AND age 22 AND name 'tomxxx' 
// user變量的值從User結構體零值變成User{Id: 114, Age:22, Name: "tomxxx", Job:"policeman"} , 注意user變量的Job字段,使用了Assign的值,數據庫的job字段變成policeman
  • FirstOrInit (參考FirstOrCreate,只是不創建數據庫記錄)

  • Select

db.Select("name, age").Find(&users)  //plain形式
db.Select([]string{"name", "age"}).Find(&users)  //string slice形式
db.Table("users").Select("COALESCE(age,?)", 42).Rows() //指定函數形式
  • Order
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

// 串聯,Multiple orders
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

// 第二個Order()中的第二個參數使用true來重寫Order規則
db.Order("age desc").Find(&users1).Order("age", true).Find(&users2)
// SELECT * FROM users ORDER BY age desc; (users1)
// SELECT * FROM users ORDER BY age; (users2)
  • Limit
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;

// 使用Limit(-1)取消數目限制
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)
  • Offset
db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;

// 使用Offset( -1)取消Offset條件
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
//  SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)
  • Count
//Count要排最后,否則會覆蓋Select的列
//這里"多即時方法"共用where條件
db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Find(&users).Count(&count)
// SELECT * from USERS WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (users)
// SELECT count(*) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (count)

db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
// SELECT count(*) FROM users WHERE name = 'jinzhu'; (count)

db.Table("deleted_users").Count(&count)
// SELECT count(*) FROM deleted_users;
  • Group 和 Having
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
for rows.Next() {
    ...
}

rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
for rows.Next() {
    ...
}

//我推薦這種,下面的Scan是將結果集掃描到定義的結構體中
type Result struct {
    Date  time.Time
    Total int64
}
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)
  • Join
rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
for rows.Next() {
    ...
}

db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)

// multiple joins with parameter
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
  • Pluck 采集
var ages []int64
db.Find(&users).Pluck("age", &ages)  //采集User中的年齡

var names []string
db.Model(&User{}).Pluck("name", &names)  

db.Table("deleted_users").Pluck("name", &names)

// 如果是多列,則應該使用Select+Find形式
db.Select("name, age").Find(&users)

  • Scan 掃描結果集到 "另一個" 結構體
type Result struct {
    Name string
    Age  int
}

var result Result
db.Table("users").Select("name, age").Where("name = ?", 3).Scan(&result)

// Raw SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)

7.關聯

  • belongs to, 屬於(多對一關系。[個人理解:由子表登記父表名,站在兒子的角度說:我屬於我的爸爸],應用舉例:多個用戶對應一個組)
type User struct {  //主表,包含兩個字段:id,name
	ID uint
	Name string
}

// `Profile` belongs to `User`, `UserID` is the foreign key
type Profile struct {  //從表,包含三個字段:id,user_id,name 
	ID uint
	UserID int  //外鍵, 默認格式為:類型名+主鍵名 這里是(UserID) 
	User   User
	Name   string
}

p := Profile{Name: "p1", User: User{Name: "tom"}}
db.Create(&p)
fmt.Printf("%+v\n", p)  //回顯{ID:2 UserID:2 User:{ID:2 Name:tom} Name:p1}
...
db.Model(&user).Related(&profile) //SELECT * FROM profiles WHERE user_id = 2; 注意,這里不會更新user變量的Name字段,所以這里的user可以寫成User{ID:2}

//上面提到外鍵的默認名稱是 類型名+主鍵名,所以下面這樣是可以的:
type User struct {
	IE   uint `gorm:"primary_key"`
	Name string
}

type Profile struct {
	ID     uint
	UserIE int  //外鍵,注意這里是UserIE
	User   User
	Name   string
}

//自定義外鍵
type User struct {
	IE   uint `gorm:"primary_key"`
	Name string
}

type Profile struct {
	ID     uint
	UserID int  
	User   User `gorm:"foreignkey:UserID"` //自定義外鍵名稱
	Name   string
}

//Association ForeignKey(從表的外鍵來源來源於主表的非主鍵)
type User struct {
	IE   uint `gorm:"primary_key"`
	Name string
}

// `Profile` belongs to `User`, `UserID` is the foreign key
type Profile struct {
	ID       uint
	User     User `gorm:"association_foreignkey:Name"` //默認用User的主鍵,將Profile中User的ID存入User的ID,這里改為用Profile中User的Name存儲到User的Name中
	UserName string  //因為上面用User的Name,所以這里就變成UserName了
	Name     string
}

  • has one, 有一個(一對一關系,語義不同[個人理解:父表中登記子表名,站在父親的角度說:我有一個兒子],應用舉例:一個人只有一張身份證)
//這里假設一個人只有一張信用卡
type User struct {    //主表,一個字段:id
    ID           uint
    CreditCard   CreditCard
}

type CreditCard struct { //從表,三個字段:id,number,user_id
    ID       uint
    Number   string
    UserID   uint  //外鍵,對應User的ID
}

u := User{CreditCard: CreditCard{Number: "6666", UserID: 3}} //這里指定UserID=3沒用,會被真正的User的ID給覆蓋掉
err := db.Create(&u).Error
fmt.Printf("%+v\n", u)   //回顯: {ID:1 CreditCard:{ID:1 Number:6666 UserID:1}}
...
db.Model(&user).Related(&card, "CreditCard") //SELECT * FROM credit_cards WHERE user_id = 123; 這里"CreditCard"可以省略,以內結構體字段名和類型名同名

//指定外鍵
type CreditCard struct {
    ID       uint
    Number   string
    UserName string  //外鍵,對應User的Name
}

type User struct {
    ID   uint
    Name string
    CreditCard CreditCard `gorm:"foreignkey:UserName"` //默認是使用has one的模型類型加上它的主鍵,這里使用UserName
}


//Association ForeignKey 和上線“屬於”中的介紹類似,更改“以哪個字段存入主表”

//Polymorphism Association,(個人理解:多主表,單子表)
Supports polymorphic has many and has one associations.

type Cat struct {
  ID    int
  Name  string
  Toy   Toy `gorm:"polymorphic:Owner;"`
}

type Dog struct {
  ID   int
  Name string
  Toy  Toy `gorm:"polymorphic:Owner;"`
}

type Toy struct {
  ID        int
  Name      string
  OwnerID   int
  OwnerType string //如果是貓擁有,這里的值就是"cats"
}
Note: polymorphic belongs-to and many-to-many are explicitly NOT supported, and will throw errors.
  • has many(單對多,一個對應零個或多個)
type User struct {
    ID          uint
    CreditCards []CreditCard  //一個人有多張卡
}

type CreditCard struct {
    ID      uint
    Number  string
    UserID  uint
}

//指定外鍵字段,略

//Association ForeignKey,略

//Polymorphism Association 多主表共享一個從表
//GORM supports polymorphic has-many and has-one associations.
type Cat struct {
  ID    int
  Name  string
  Toy   []Toy `gorm:"polymorphic:Owner;"`
}

type Dog struct {
  ID   int
  Name string
  Toy  []Toy `gorm:"polymorphic:Owner;"`
}

type Toy struct {
  ID        int
  Name      string
  OwnerID   int
  OwnerType string  //取值"cats"或"dogs"
}
Note: polymorphic belongs-to and many-to-many are explicitly NOT supported, and will throw errors.

//使用
db.Model(&user).Related(&emails)
// SELECT * FROM emails WHERE user_id = 111; // 111 is user's primary key
  • 自動創建更新
自動創建/更新
創建/更新記錄時, GORM 將自動保存關聯及其引用。如果關聯具有主鍵, GORM 將調用 Update 來保存它, 否則將創建它。

user := User{
    Name:            "jinzhu",
    BillingAddress:  Address{Address1: "Billing Address - Address 1"},
    ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
    Emails:          []Email{
        {Email: "jinzhu@example.com"},
        {Email: "jinzhu-2@example.com"},
    },
    Languages:       []Language{
        {Name: "ZH"},
        {Name: "EN"},
    },
}

db.Create(&user)
// BEGIN TRANSACTION;
// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1");
// INSERT INTO "addresses" (address1) VALUES ("Shipping Address - Address 1");
// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2);
// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com");
// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu-2@example.com");
// INSERT INTO "languages" ("name") VALUES ('ZH');
// INSERT INTO user_languages ("user_id","language_id") VALUES (111, 1);
// INSERT INTO "languages" ("name") VALUES ('EN');
// INSERT INTO user_languages ("user_id","language_id") VALUES (111, 2);
// COMMIT;


//跳過自動更新(只更新引用):
//如果數據庫中已存在關聯, 你可能不希望對其進行更新。
//可以使用 DB 設置, 將 gorm: association_autoupdate 設置為 false
// Don't update associations having primary key, but will save reference
db.Set("gorm:association_autoupdate", false).Create(&user)
db.Set("gorm:association_autoupdate", false).Save(&user)

//或者使用 GORM tags gorm:"association_autoupdate:false"
type User struct {
  gorm.Model
  Name       string
  CompanyID  uint
  // Don't update associations having primary key, but will save reference
  Company    Company `gorm:"association_autoupdate:false"`
}
  • 多對多(待完善)
Many To Many
Many to Many adds a join table between two models.

For example, if your application includes users and languages, and a user can speak many languages, and many users can speak a specfied language.

// User has and belongs to many languages, use `user_languages` as join table
type User struct {
    gorm.Model
    Languages         []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
    gorm.Model
    Name string
}
Back-Reference
// User has and belongs to many languages, use `user_languages` as join table
type User struct {
    gorm.Model
    Languages         []*Language `gorm:"many2many:user_languages;"`
}

type Language struct {
    gorm.Model
    Name string
    Users             []*User     `gorm:"many2many:user_languages;"`
}

var users []User
language := Language{}

db.First(&language, "id = ?", 111)

db.Model(&language).Related(&users,  "Users")
// SELECT * FROM "users" INNER JOIN "user_languages" ON "user_languages"."user_id" = "users"."id" WHERE  ("user_languages"."language_id" IN ('111'))
Foreign Keys
type CustomizePerson struct {
  IdPerson string             `gorm:"primary_key:true"`
  Accounts []CustomizeAccount `gorm:"many2many:PersonAccount;association_foreignkey:idAccount;foreignkey:idPerson"`
}

type CustomizeAccount struct {
  IdAccount string `gorm:"primary_key:true"`
  Name      string
}
It will create a many2many relationship for those two structs, and their relations will be saved into join table PersonAccount with foreign keys customize_person_id_person AND customize_account_id_account

Jointable ForeignKey
If you want to change join table’s foreign keys, you could use tag association_jointable_foreignkey, jointable_foreignkey

type CustomizePerson struct {
  IdPerson string             `gorm:"primary_key:true"`
  Accounts []CustomizeAccount `gorm:"many2many:PersonAccount;foreignkey:idPerson;association_foreignkey:idAccount;association_jointable_foreignkey:account_id;jointable_foreignkey:person_id;"`
}

type CustomizeAccount struct {
  IdAccount string `gorm:"primary_key:true"`
  Name      string
}
Self-Referencing
To define a self-referencing many2many relationship, you have to change association’s foreign key in the join table.

to make it different with source’s foreign key, which is generated using struct’s name and its primary key, for example:

type User struct {
  gorm.Model
  Friends []*User `gorm:"many2many:friendships;association_jointable_foreignkey:friend_id"`
}
GORM will create a join table with foreign key user_id and friend_id, and use it to save user’s self-reference relationship.

Then you can operate it like normal relations, e.g:

DB.Preload("Friends").First(&user, "id = ?", 1)

DB.Model(&user).Association("Friends").Append(&User{Name: "friend1"}, &User{Name: "friend2"})

DB.Model(&user).Association("Friends").Delete(&User{Name: "friend2"})

DB.Model(&user).Association("Friends").Replace(&User{Name: "new friend"})

DB.Model(&user).Association("Friends").Clear()

DB.Model(&user).Association("Friends").Count()
Working with Many To Many
db.Model(&user).Related(&languages, "Languages")
// SELECT * FROM "languages" INNER JOIN "user_languages" ON "user_languages"."language_id" = "languages"."id" WHERE "user_languages"."user_id" = 111

// Preload Languages when query user
db.Preload("Languages").First(&user)
  • 關聯模式(主要是為了使用它的helper方法)
Association Mode contains some helper methods to handle relationship related things easily.

// Start Association Mode
var user User
db.Model(&user).Association("Languages")
// `user` is the source, must contains primary key //user是源,必須包含主鍵
// `Languages` is source's field name for a relationship //languages是user的字段
// AssociationMode can only works if above two conditions both matched, check it ok or not:  //關聯模式必須滿足上述兩個條件
// db.Model(&user).Association("Languages").Error

查找關聯
查找匹配的關聯

db.Model(&user).Association("Languages").Find(&languages)
添加關聯
為many to many,has many添加新的關聯關系代替當前的關聯關系has one,belongs to

db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Append(Language{Name: "DE"})

//替換關聯 (關聯要有主鍵,否則就變成新增[原有關聯還在,只是其"外鍵"清空了])
db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)

//刪除關聯 刪除關聯的引用,不會刪除關聯本身 (關聯要有主鍵)
db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN}) 
db.Model(&user).Association("Languages").Delete(languageZH, languageEN)

//清空關聯, 清空對關聯的引用,不會刪除關聯本身 (關聯要有主鍵)
db.Model(&user).Association("Languages").Clear()

db.Model(&user).Association("Languages").Count()
  • 預加載(加載結構體下的結構體、或結構體下的數組)
//Find()提供外鍵列表,這個列表作為從表的過濾條件
db.Preload("Orders").Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4);

//Preload第一個參數之后的參數作為第一參數的where語句
db.Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4) AND state NOT IN ('cancelled');

//這里的where作用於Find()的參數
db.Where("state = ?", "active").Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
// SELECT * FROM users WHERE state = 'active';
// SELECT * FROM orders WHERE user_id IN (1,2) AND state NOT IN ('cancelled');

//多個字段需要居家在,則可以串起來
db.Preload("Orders").Preload("Profile").Preload("Role").Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4); // has many
// SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // has one
// SELECT * FROM roles WHERE id IN (4,5,6); // belongs to

//Auto Preloading
//Always auto preload associations
type User struct {
  gorm.Model
  Name       string
  CompanyID  uint
  Company    Company `gorm:"PRELOAD:false"` // not preloaded,這個只對Set()起作用,對強制Preload()不生效
  Role       Role                           // preloaded
}
db.Set("gorm:auto_preload", true).Find(&users)

//Nested Preloading, 選擇性的加載"結構體.結構體"
db.Preload("Orders.OrderItems").Find(&users)
db.Preload("Orders", "state = ?", "paid").Preload("Orders.OrderItems").Find(&users)

//Custom Preloading SQL 定制預加載行為
//You could custom preloading SQL by passing in func(db *gorm.DB) *gorm.DB, for example:

db.Preload("Orders", func(db *gorm.DB) *gorm.DB {
    return db.Order("orders.amount DESC")
}).Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4) order by orders.amount DESC;

8.鏈式操作

//Method Chaining,Gorm 實現了鏈式操作接口,所以你可以把代碼寫成這樣:
db, err := gorm.Open("postgres", "user=gorm dbname=gorm sslmode=disable")

// 創建一個新的 relation, 注意:這里賦值給tx
tx := db.Where("name = ?", "jinzhu")

// 添加更多查詢條件, 注意:這里賦值給tx
if someCondition {
    tx = tx.Where("age = ?", 20)
} else {
    tx = tx.Where("age = ?", 30)
}

if yetAnotherCondition {
    tx = tx.Where("active = ?", 1)
}
//在調用立即執行方法前不會生成 Query 語句,有時候這會很有用。
//比如你可以抽取一個函數來處理一些通用邏輯。

//立即執行方法 : 相對鏈式方法而言
//Immediate methods ,立即執行方法是指那些會立即生成 SQL //語句並發送到數據庫的方法, 他們一般是 CRUD 方法,比如:
//Create, First, Find, Take, Save, UpdateXXX, Delete, Scan, Row, Rows…

9.范圍

//Scopes()的入參是個func(db *gorm.DB) *gorm.DB
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
    return db.Where("amount > ?", 1000)
}

func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
    return db.Where("pay_mode_sign = ?", "C")
}

func PaidWithCod(db *gorm.DB) *gorm.DB {
    return db.Where("pay_mode_sign = ?", "C")
}

func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {
    return func (db *gorm.DB) *gorm.DB {
        return db.Scopes(AmountGreaterThan1000).Where("status IN (?)", status)
    }
}

db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// 查詢所有信用卡中金額大於 1000 的訂單

db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
// 查詢所有 Cod 中金額大於 1000 的訂單

db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// 查詢所有已付款、已發貨中金額大於 1000 的訂單

10.多個立即執行方法的注意事項

//多個立即執行方法
//Multiple Immediate Methods,在 GORM //中使用多個立即執行方法時,后一個立即執行方法會復用前一個 立即執行方法的條件 (不包括內聯條件) 。

db.Where("name LIKE ?", "jinzhu%").Find(&users, "id IN (?)", []int{1, 2, 3}).Count(&count)
生成的 Sql

//SELECT * FROM users WHERE name LIKE 'jinzhu%' AND id IN (1, 2, 3)

//SELECT count(*) FROM users WHERE name LIKE 'jinzhu%'

11.錯誤處理

//處理數據時,通常會發生多個錯誤。 GORM提供了一個API來將所有錯誤作為切片返回:
// 如果發生了一個以上的錯誤, `GetErrors` 以`[]error`形式返回他們
errors := db.First(&user).Limit(10).Find(&users).GetErrors()

fmt.Println(len(errors))

for _, err := range errors {
  fmt.Println(err)
}

//未找到,gorm.DB.RecordNotFound()返回布爾值
if db.Model(&user).Related(&credit_card).RecordNotFound() {
  // 未找到記錄
}

12.鈎子

省略,就是創建前后,更新前后,刪除前后,查詢前后調用的鈎子
可以自定義鈎子,注冊即可使用

13.事務

// 開啟事務
tx := db.Begin()

// 在事務中執行具體的數據庫操作 (事務內的操作使用 'tx' 執行,而不是 'db')
tx.Create(...)
// ...
// 如果發生錯誤則執行回滾
tx.Rollback()
// 或者(未發生錯誤時)提交事務
tx.Commit()

14.模型方法

判斷表存在與否,建表,刪表,修改列數據類型,刪除列,增刪索引,增刪外鍵

15.原生SQL

db.Exec("DROP TABLE users;")
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN (?)", time.Now(), []int64{11,22,33})

// Scan
type Result struct {
    Name string
    Age  int
}

var result Result
db.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)

//gorm.DB.Row()單行,Rows()是多行
row := db.Table("users").Where("name = ?", "jinzhu").Select("name, age").Row() // (*sql.Row)
row.Scan(&name, &age)

16.復合主鍵

type Product struct {
   ID           string `gorm:"primary_key"`
   LanguageCode string `gorm:"primary_key"`
   Code         string
   Name         string
}

17.Logger

//Logger
//Gorm有內置的日志記錄器支持,默認情況下,它會打印發生的錯誤

// 啟用Logger,顯示詳細日志
db.LogMode(true)

// 禁用日志記錄器,不顯示任何日志
db.LogMode(false)

// 調試單個操作,顯示此操作的詳細日志
db.Debug().Where("name = ?", "jinzhu").First(&User{})
//自定義 Logger
//參考GORM的默認記錄器如何自定義它 https://github.com/jinzhu/gorm/blob/master/logger.go

//例如,使用Revel的Logger作為GORM的輸出

db.SetLogger(gorm.Logger{revel.TRACE})
//使用 os.Stdout 作為輸出

db.SetLogger(log.New(os.Stdout, "\r\n", 0))


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM