1. 程式人生 > 實用技巧 >go語言:GORM

go語言:GORM

7.GORM

  • 物件關係對映。

    資料表  -> 結構體
    資料行  -> 結構體例項
    欄位   -> 結構體欄位
    
  • 優點:提高開發效率,缺點犧牲執行效能,靈活性,弱化SQL能力。

  • gorm下載: 官網

go get -u github.com/jinzhu/gorm

7.1資料庫的連線

// 引包
import (
	 _ "github.com/jinzhu/gorm/dialects/mysql"
	"github.com/jinzhu/gorm"
)
// 結構體建立
type UserInfo struct {
	Id uint
	Name string
	Gender string
	Hobby string
}
  • 連線資料庫
// 連線資料庫 這裡指定編碼格式,可解析時間型別,時間取當地時間
	db, err :=gorm.Open("mysql", "root:123@(ip:3306)/db1?charset=utf8mb4&parseTime=True&loc=Local")
	if err != nil{
		panic(err)
	}
// 關閉連線
	defer db.Close()

7.2資料庫遷移和最簡單CURD

  • 建立表 自動遷移。讓結構體和資料表形成對應關係
db.AutoMigrate(&UserInfo{})
  • 插入一條資料
/ 建立資料行
	user1 := UserInfo{1,"jk","man","籃球"}
	db.Create(&user1)
  • 查詢第一條資料
var u UserInfo
db.First(&u)
fmt.Println(u)
  • 更新一條資料
var u UserInfo
// 查詢到第一條資料,然後更新
db.First(&u)
db.Model(&u).Update("hobby","running")
  • 刪除操作
db.Delete(&u)

7.3 GORM模型定義

  • GORM 內建一個gorm.Model結構體,gorm.Model是一個包含了ID,CreteAt,UpdateAt,DeleteAt四個字端的Golang結構體。
type Model struct {
  ID uint `gorm:"primary_key"`
  CreateAt time.Time
  UpdateAt time.Time
  DeleteAt *time.Time
}
  • 你可以自行將它嵌入到你自己模型中:
type User struct {
	gorm.Model
  Name string
}
  • 模型定義示例
type User struct{
	gorm.Model// 內嵌Model
	Name string
	Age sql.NullInt64//零值型別
	Birthday *time.Time
	Email string `gorm:"type:varchar(100);unique_index"`
	Role string `gorm:"size:255"`// 設定字端大小255
	MemberNumber string `gorm:"unique;not null"`// 設定會員號,唯一且不為空
	Num int `gorm:"AUTO_INCREMENT"`//設定num為自增型別
	Address string `gorm:"index:addr"`// 給address設定索引為addr
	IgnoreMe int `gorm:"-"`//忽略本字端
}
  • 自定義欄位為主鍵
type Animal struct {
  AnimalID int64 `gorm:"primary_key"`
  Name string
}
  • 表的名字預設是結構體複數,你也可以自定義表的名字
type Animal struct {
	AnimalID int64 `gorm:"primary_key"`
	Name string
}

// 自定義表名字,因唯一指定表名,不會受方法影響
func (Animal) TableName() string{
	return "self_animal"
}
  • 禁用表的複數形式
db.SingularTable(true)
  • 強行指定表名
// 使User結構體建立名叫 mysql_self_name表
db.Table("mysql_self_name").CreateTable(&User{})
  • 通過自定義方法設計表名
gorm.DefaultTableNameHandle = func (db *gorm.DB, defaultTableName string) string{
  return "SMS_" + defaultTableName
}
// 給建立的預設表名,加字首SMS
  • 自定義列的名稱
type User struct {
  Age sql.NullInt64 `gorm:"column:user_age"`
}
// 會新增一個user_age字端。
  • 時間型別

    CreatedAt

db.Create(&user)
db.Model(&user).Update("CreatedAt", time.Now())

​ UpdatedAt

db.Save(&user)
db.Model(&user).Update("name","xjk")

​ DeletedAt

  • 呼叫刪除記錄,將會設定DeletedAt字端為當前時間,而不是直接將記錄從資料庫中刪除。

7.4 GORM 的CURD

  • 首先定義一個模型
type uGroup struct {
	Id int64
	Name string `gorm:"defalut:'wang'"`
	Age int64
}

7.4.1建立記錄

  • NewRecord
var u = type uGroup struct {
	Id int64
	Name string `gorm:"defalut:'wang'"`
	Age int64
}{Name:"laobing2", Age:77}
pk := db.NewRecord(&u)
fmt.Println(pk)// true
// NewRecord 查詢當主鍵為空返回true

// 建立使用者
db.Create(&u)
pk2 := db.NewRecord(u)
// 此時使用者已建立,已經有主鍵
fmt.Println(pk2)// false

7.4.2 預設值

  • 通過tag定義欄位的預設值。
type uGroup struct {
   Id int64
   Name string `gorm:"default:'ss'"`
   Age int64
}
  • 值得注意是,在建立記錄生成SQL語句會排除沒有值或值為零的欄位。在將記錄插入到資料庫後,Gorm會從資料庫載入到那些字端預設值。
var u = uGroup{Name:"",Age:99}
db.Create(&u)
  • 所有欄位零值,如: 0 "" false 或者其他零值。都不會儲存到資料庫中,但會使用他們預設值。若果想要避免此情況,可以考慮使用指標.
  • 方式1:指標方式實現
type uGroup struct {
	Id int64
	Name *string `gorm:"default:'ss'"`
	Age int64
}
var u = uGroup{Name:new(string), Age:77}
db.Create(&u)
  • 方式2:Scanner/Valuer介面方式
type uGroup struct {
	Id int64
	Name sql.NullString `gorm:"default:'ss'"`
	Age int64
}
var u = uGroup{Name:sql.NullString{"", true}, Age:55}
db.Create(&u)

7.4.3普通查詢

  • 一般查詢
var u uGroup
// 根據主鍵查詢
db.First(&u)
// 獲取第一條記錄
db.Take(&u)
// 根據主鍵查詢最後一條記錄
db.Last(&u)
// 查詢所有記錄
var us []uGroup
db.Find(&us)
// 查詢主鍵id=3記錄
var u uGroup
db.First(&u, 3)

7.4.4 Where查

  • 普通SQL查詢
var u uGroup
// 查詢name=ss 的第一條記錄
db.Where("name=?","ss").First(&u)
// 查詢 所有滿足name=ss的記錄
var us []uGroup
db.Where("name=?","ss").Find(&us)
// 查詢 name <>
db.Where("name <> ?","ss").Find(&us)
// IN 查詢
db.Where("name IN (?)", []string{"ss","jj"}).Find(&us)
// LIKE
db.Where("name LIKE ?","%ss%").Find(&us)
// AND
db.Where("name = ? AND age >= ?", "ss", "25").Find(&us)
// 時間Time
db.Where("updated_at > ?", lastWeek).Find(&us)
db.Where("created_at BETWEEN ? AND ?",last, yest).Find(&us)

7.4.5 struct & Map查詢

  • Struct demo
var u uGroup
// 查詢name=ss,age=20
db.Where(&uGroup{Name:"ss",Age:20}).First(&u)
  • Map查詢
// Map查詢
var us []uGroup
db.Where(map[string]interface{}{"name":"ss","age":77}).Find(&us)
// SELECT * FROM u_groups WHERE name = "ss" AND age = 77;
  • 主鍵的切片
db.Where([]int64{1,2,5}).Find(&us)
// SELECT * FROM u_groups WHERE id IN (1, 2, 5)
  • 當通過結構體進行查詢時,GORM將會只通過非零值欄位查詢。這意味著如果你的欄位值為0,"",false或者其他零值時,將不會被用於構建查詢條件。
db.Where(&User{name:"ss",Age:0}).Find(&us)
  • 不過你可以通過上面提到2個方法。通過指標或是Scanner/Valuer

7.4.6Not條件查詢

db.Not("name","ss").First(&u)
// SELECT * FROM u_groups WHERE name <> "ss" LIMIT 1

// Not In
db.Not("name", []string{"ss","1234"}).Find(&us)

// 主鍵不在切片中
db.Not([]int64{1,2,3}).First(&u)

// 查詢第一個
db.Not([]int64{}).First(&u)
// Struct
db.Not(uGroup{Name:"xx"}).First(&u)

7.4.7 Or條件查詢

db.Where("name=?","ss").Or("age=?","43").Find(&us)
//SELECT * FROM u_groups WHERE name = 'ss' OR age = 43;
// Struct
db.Where("name='jk'").Or(uGroup{Name:"ming"}).Find(&us)
// Map
db.Where("name='jk'").Or(map[string]interface{}{"name":"ming"}).Find(&us)
//SELECT * FROM u_groups WHERE name = 'ss' OR name = 'ming';

7.4.8內聯條件

  • 作用與Where查詢類似,當內聯條件與多個立即執行方法一起使用時,內聯條件不會傳遞給後面立即執行。
db.First(&u,5)//適用整形
db.First(&u,"id=?","string_primary_key")//適用菲整形主鍵
db.Find(&u,"name <> ? AND age > ?","jk", 20)
// struct
db.Find(&us,uGroup{Age:20})
// SELECT * FROM u_groups WHERE age=20;

// Map
db.Find(&u,map[string]interface{}{"age":20})
// SELECT * FROM u_groups WHERE age=20;

7.4.9額外查詢選項

  • 為查詢SQL新增額外的SQL操作
db.Set("gorm:query_option","FOR UPDATE").First(&u,5)
// SELECT * FROM u_groups WHERE id=5 FOR UPDATE;

7.4.10FirstOrInit

  • 獲取匹配的第一條記錄,否則根據給定的條件初始化一個新的物件(僅支援struct 和 map 條件)
// 當沒有找到
db.FirstOrInit(&u, uGroup{Name:"not exist"})
fmt.Println(u)//返回結果:{0 not exist 0}
// 當找到了
db.Where(uGroup{Name: "ss"}).FirstOrInit(&u)
fmt.Println(u)//返回結果: {1 ss 77}

// 通過map獲取
db.FirstOrInit(&u,map[string]interface{}{"name":"ss"})
  • Attrs
    • 如果記錄未找到,將使用引數初始化struct
// 1.未找到:
db.Where(uGroup{Name:"not exist"}).Attrs(uGroup{Age:22}).FirstOrInit(&u)
fmt.Println(u)// {0 not exist 22}
// 當你查詢使用者名稱: not exist不存在時候會 預設返回User{Name: "not exist", Age: 20}

// 當然你也可以這麼寫
db.Where(uGroup{Name:"not exist"}).Attrs("age", 20).FirstOrInit(&u)

// 2.找到了
相同寫法。
  • Assign
    • 無論是否查詢到,都將引數賦值給struct
// 未找到
db.Where(uGroup{Name:"not exist"}).Assign(uGroup{Age:22}).FirstOrInit(&u)
fmt.Println(u)// {0 not exist 22}
// 找到
db.Where(uGroup{Name:"ss"}).Assign(uGroup{Age:77}).FirstOrInit(&u)
fmt.Println(u)// {1 ss 77}

7.4.11FirstOrCreate

  • 獲取匹配的第一條記錄,否則根據給定的條件建立一個新的記錄(僅支援struct和map條件)
// 未找到
db.FirstOrCreate(&u, uGroup{Name:"not exist"})
fmt.Println(u)// 沒有找到,直接建立了:{15 not exist 0}
// 找到
db.Where(uGroup{Name: "ss"}).FirstOrCreate(&u)
fmt.Println(u)//{1 ss 77}
  • Attrs
    • 如果記錄未找到,將使用引數建立struct 和記錄
db.Where(uGroup{Name:"Tom"}).Attrs(uGroup{Age:20}).FirstOrCreate(&u)
fmt.Println(u) //未找到會建立: {16 Tom 20},找到了會返回結果
  • Assign
    • 不管記錄是否找到,豆漿引數賦值給struct並儲存資料庫
db.Where(uGroup{Name:"Lucy"}).Assign(uGroup{Age:30}).FirstOrCreate(&u)
fmt.Println(u) // 未找到建立:{17 Lucy 30}

db.Where(uGroup{Name:"Lucy"}).Assign(uGroup{Age:32}).FirstOrCreate(&u)// 找到了,更改使用者資訊,age改為32

7.5GORM高階查詢

7.5.1子查詢

  • 通過*gorm.expr的子查詢
type Order struct {
	DeletedAt interface{}
	State string
	Amount int
}
var orders Order
	// SELECT * FROM "orders"  WHERE "orders"."deleted_at" IS NULL AND (amount > (SELECT AVG(amount) FROM "orders"  WHERE (state = 'paid')));
db.Where("amount > ?",db.Table("orders").Select("AVG(amount)").Where("state = ?","sk2").SubQuery()).Find(&orders)
	fmt.Println(rest)
fmt.Println(orders)

7.5.2選擇欄位

  • Select 指定你想從資料庫中檢索的欄位,預設會選擇全部欄位
var userinfo []uGroup
// 方式1
db.Select("name,age").Find(&userinfo)
fmt.Println(userinfo)
//// SELECT name, age FROM users;
// 方式2
db.Select([]string{"name","age"}).Find(&userinfo)
fmt.Println(userinfo)
// 方式3
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
//// SELECT COALESCE(age,'42') FROM users;

7.5.3排序

  • Order 指定從資料庫中檢索出記錄順序。設定第二個引數reorder為true ,可以覆蓋前面定義的排序條件。
var users []uGroup
db.Order("age desc,name").Find(&users)
fmt.Println(users)
// SELECT * FROM u_group order by age desc,name

// 多欄位排序
db.Order("age desc").Order("name").Find(&users)
fmt.Println(users)
/ SELECT * FROM users ORDER BY age desc, name;

// 覆蓋排序
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)

7.5.4數量

  • limit 指定資料庫檢索的最大記錄數。
var users []uGroup
db.Limit(2).Find(&users)
fmt.Println(users)
// SELECT * FROM users LIMIT 3;

// -1 取消Limit條件
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
//// SELECT * FROM users LIMIT 10; (users1)
//// SELECT * FROM users; (users2)

7.5.5偏移

  • Offset,指定開始返回記錄前要跳過的記錄數。
var users []uGroup
db.Limit(2).Offset(1).Find(&users)
fmt.Println(users)
// SELECT * FROM u_groups limit 3 offset 1;

7.5.6統計總數

var users []uGroup
var count int
db.Where("name = ?", "xiaoming2").Or("name = ?","xiaoming3").Find(&users).Count(&count)
fmt.Println(users,count)
// [{2 xiaoming2 13} {3 xiaoming3 23}] 2


db.Model(&uGroup{}).Where("name = ?","xiaoming2").Count(&count)
fmt.Println(count)
// SELECT count(*) FROM users WHERE name = "xiaoming2"

db.Table("u_groups").Count(&count)
// SELECT count(*) FROM u_groups;

db.Table("u_groups").Select("count(distinct(name))").Count(&count)
//// SELECT count( distinct(name) ) FROM u_groups;
  • 注意:Count 必須是鏈式查詢的最後一個操作,因為它會覆蓋前面SELECT,但如果裡面使用count時就不會覆蓋。

7.5.7Group & Having

  • 示例1:
type Result struct{
	State string
	Total int
}

rows, err := db.Table("orders").Select("state,sum(amount) as total").Group("state").Rows()
	for rows.Next() {
		r := &Result{}
		err = db.ScanRows(rows,r)
		fmt.Println(r)
		if err != nil{
			fmt.Println(err)
			break
		}
	}
  • 示例2:
var res []Result
db.Table("orders").Select("state,sum(amount) as total").Group("state").Scan(&res)
fmt.Println(res)
  • 示例三
// Having
rows, err := db.Table("orders").Select("state,sum(amount) as total").Group("state").Having("sum(amount) > ?",30).Rows()
	if err != nil{
		fmt.Println(err)
	}
	for rows.Next() {
		r := &Result{}
		err = db.ScanRows(rows,r)
		if err != nil {
			fmt.Println(err)
			break
		}
		fmt.Println(*r)
	}
  • 示例四:
var res []Result
db.Table("orders").Select("state,sum(amount) as total").Group("state").Having("sum(amount) > ?",30).Scan(&res)
fmt.Println(res)

7.5.8連線

  • 通過Join進行連線。
type uGroup struct {
	Id int64
	Name string `gorm:"default:'ss'"`
	Age int64
}
type Email struct {
	Id int64
	Email string
	UserId int64
}
type Result2 struct {
		Uname string
		Ename string
}

	rows, err := db.Table("u_groups").Select("u_groups.name as uname, emails.email as ename").Joins("left join emails on emails.id = u_groups.id").Rows()
	for rows.Next(){
		r := &Result2{}
		err = db.ScanRows(rows,r)
		if err != nil{
			fmt.Println(err)
			break
		}
		fmt.Println(*r)
	}
  • 示例2:
type Result2 struct {
		Uname string
		Ename string
	}
var results []Result2
db.Table("u_groups").Select("u_groups.name as uname, emails.email as ename").Joins("right join emails on emails.id = u_groups.id").Scan(&results)
fmt.Println(results)
  • 示例3:
var u uGroup
db.Joins("JOIN emails ON emails.id = u_groups.id AND emails.email = ?", "[email protected]").Joins("JOIN credit_cards ON credit_cards.id=u_groups.id").Where("credit_cards.common=?","common1").Find(&u)
fmt.Println(u)

7.5.9Pluck

  • Pluck 查詢model中的一個列作為切片,如果想要查詢多個列可以收納櫃Scan
var ages []int64
var users []uGroup
db.Find(&users).Pluck("age",&ages)
fmt.Println(ages)
  • Model指定表Struct
var names []string
db.Model(&uGroup{}).Pluck("name",&names)
fmt.Println(names)
  • 查詢多個列
db.Select("name,age").Find(&users)
fmt.Println(users)

7.5.10掃描

  • Scan掃描結果至一個struct
type Result struct {
		Name string
		Age int
	}
var result Result
db.Table("u_groups").Select("name,age").Where("name=?","xiaoming").Scan(&result)
fmt.Println(result)
  • 示例2
var results []Result
db.Table("u_groups").Select("name,age").Where("id > ?",0).Scan(&results)
fmt.Println(results)
  • 示例3:
//原生SQL
db.Raw("SELECT name,age from u_groups WHERE name=?","xiaoming").Scan(&results)
fmt.Println(results)

7.5.11 鏈式操作相關

  • 鏈式操作,Gorm實現了鏈式操作介面,所以你可以把程式碼寫成這樣。
var results []Result
	tx := db.Where("name = ?","xiaoming")
	// 新增更多條件
	judge:=true
	if judge {
		tx = tx.Where("age > ?",5)
	} else {
		tx = tx.Where("age = ?",30)
	}
	tx.Find(&results)
	fmt.Println(results)
  • 在呼叫立即執行方法前不能生成Query語句,藉助這個特性你可以i建立一個函式處理一些普通邏輯。

7.5.12範圍

  • Scopes,Scopes 是建立在鏈式操作的基礎之上。基於它,你可以抽取一些通用邏輯,寫出更多可重用的函式庫。
func UGroupAge20(db *gorm.DB) *gorm.DB{
	return db.Where("age > ?", 20)
}

func NameeqXiaoming(db *gorm.DB) *gorm.DB{
	return db.Where("name = ?","xiaoming")
}
func UGroupIdeq1(db *gorm.DB) *gorm.DB{
	return db.Where("id = ?", 1)
}

func uGroupInId(ids []int) func(db *gorm.DB) *gorm.DB{
	return func(db *gorm.DB) *gorm.DB {
		return db.Scopes(UGroupAge20).Where("id IN (?)",ids)
	}
}
var us []uGroup

db.Scopes(UGroupAge20,NameeqXiaoming,UGroupIdeq1).Find(&us)
fmt.Println(us)
db.Scopes(uGroupInId([]int{1,2})).Find(&us)
fmt.Println(us)

7.5.13多個立即執行

  • 在GORM中使用多個立即執行方法時,後一個立即執行方法會服用前一個立即執行方法的條件(不包含內聯條件)
var users []uGroup
var count int64
db.Where("name Like ?", "xiaoming%").Find(&users,"id IN (?)", []int{1,2,3}).Count(&count)
fmt.Println(users)
fmt.Println(count)

7.6 更新

7.6.1更新所有欄位

  • 使用Save() 預設會更新所有欄位,即使你沒有去賦值。
var user uGroup
	db.First(&user)
	fmt.Println(user)
	user.Name = "jk"
	user.Age = 22
	db.Save(&user)

7.6.2 更新修改欄位

  • 使用Update或者Updates更新指定欄位
// 更新單個屬性,如果它有變化
db.Model(&user).Update("name","hello")
// 根據條件更新單個屬性
db.Model(&user).Where("age = ?",21).Update("name","jk")
// 使用map更新多個屬性,只會更新其中有變化屬性
db.Model(&user).Updates(map[string]interface{}{"name":"xujunkai","age":18})
// 使用struct 更新多個屬性,只會更新其中變化且非零的欄位
db.Model(&user).Updates(uGroup{Name:"xjk",Age: 20})
// struct更新時,GORM只會更新那些非零值的欄位。
// 對於 "" 0 false 不會發生任何更新
db.Model(&user).Updates(uGroup{Name:"",Age: 0})

7.6.3 更新選定欄位

  • 如果你想更新或忽略某些欄位,你可以使用Select,Omit
// 下面我們在map中更新name,age.但是我們只選擇了name.所以只更新name
db.Model(&user).Select("name").Updates(map[string]interface{}{"name":"xujunkai","age":19})


// Omit 表示要忽略欄位
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name":"xiaoming","age":30})

7.6.4無Hooks更新

  • 上面更新操作會自動執行Model的。BeforeUpdate,AfterUpdate方法。更新UpdateAt時間戳。更新時儲存其Associations.如果你想呼叫這些方法,你可以使用UPdateColume,UpdateColumns
db.First(&user)
db.Model(&user).UpdateColumn("name","hello")
// 更新多個屬性。類似Updates
db.Model(&user).UpdateColums(User{Name:"hello",Age:18})

7.6.5批量更新

  • 批量更新時,Hook(鉤子函式)。不會更新。
// map方式批量更新
db.Table("u_groups").Where("id IN (?)",[]int{1,5,11}).Updates(map[string]interface{}{"name":"xjk","age":33})

// struct 批量更新 只會更新非零欄位。如果想更新所有欄位。請使用map
db.Model(uGroup{}).Updates(uGroup{Name:"xiaoming",Age: 15})

// RowsAffected 獲取更新記錄
res := db.Model(uGroup{}).Updates(uGroup{Name: "xjk",Age: 40}).RowsAffected
fmt.Println(res)

7.6.6使用SQL表示式

  • 查詢表中第一條資料,然後更新
ar user uGroup
db.First(&user)


db.Model(&user).Update("age",gorm.Expr("age*?+?",4,1))
// UPDATE `u_groups` SET `age` = age * 4 + 1, `updated_at` = '2020-08-01 13:10:20'  WHERE `users`.`id` = 1;

db.Model(&user).Updates(map[string]interface{}{"age":gorm.Expr("age - ?",500)})

db.Model(&user).Where("age > 10").UpdateColumn("age",gorm.Expr("age - ?",5))

7.6.7其他更新選項

// 為 update SQL新增其他SQL
db.Model(&user).Set("gorm:update_option","OPTION (OPTIMIZE FOR UNKNOWN)").Update("name","hello")

7.7刪除

7.7.1刪除記錄

  • 警告 刪除記錄時,請確保主鍵欄位有值。GORM會通過主鍵去刪除記錄,如果主鍵為空,GORM會刪除該model所有記錄.
var user uGroup
db.First(&user)
db.Delete(&user)

7.7.2批量刪除

  • 刪除全部匹配記錄
db.Where("email LIKE ?","%xjk%").Delete(uGroup{})

7.7.3軟刪除

  • 如果一個model有,DeletedAt欄位,他將自動獲得軟刪除功能,當呼叫Delete方法時,記錄不會真正的從資料庫中被刪除,只會DeletedAt欄位值會被設定為當前時間
db.Delete(&user)

// 批量刪除
db.Where("age = ?",20).Delete(&u_Group{})

// 查詢會忽略被軟刪除的記錄
db.Where("age = ?",84).Find(&user)

// 通過Unscoped方法可以查詢被軟刪除的記錄
var users []uGroup
db.Unscoped().Where("age=?",84).Find(&users)
fmt.Println(users)

7.7.4物理刪除

db.Unscoped().Delete(&user)