天天看點

[Golang]Gorm使用彙總資料安裝資料庫配置資料庫連接配接模型定義基礎操作回調使用(Callback)異常處理事務日志參考

Gorm使用彙總

  • 資料
  • 安裝
  • 資料庫配置
  • 資料庫連接配接
    • sqlite3
    • mysql
  • 模型定義
    • tag:gorm
    • 表名
  • 基礎操作
    • 新增
      • NewRecord主鍵檢查 & Create
    • 查詢
      • First:查詢第一條記錄
      • Last:查詢最後一條記錄
      • First(... , pk):根據主鍵查詢記錄
      • Where(...) 條件查詢條件
      • FirstOrInit() & Attrs() & Assign()
      • FirstOrCreate() & Attrs() & Assign()
      • Select查詢字段定義
      • Scan
      • Scopes 動态條件添加
    • 修改
      • Save
      • Update & Updates & Omit
    • 删除
  • 回調使用(Callback)
    • Callback預設定義
    • Callback事務保證
  • 異常處理
  • 事務
  • 日志
  • 參考

資料

比較好的Gorm中文文檔 https://jasperxu.com/gorm-zh/,本文基于該資料進行整理,彙總最基本的Gorm入門使用内容,陸續補充。

安裝

go get -u github.com/jinzhu/gorm
           

資料庫配置

//資料庫配置資訊
func options() {
	// 全局禁用表名複數
	// 如果設定為true,`User`的預設表名為`user`,使用`TableName`設定的表名不受影響
	DB.SingularTable(true)
	//自動遷移模式将保持更新到最新。
	//警告:自動遷移僅僅會建立表,缺少列和索引,并且不會改變現有列的類型或删除未使用的列以保護資料。
	DB.AutoMigrate(&User{})
	//列印日志,預設是false
	DB.LogMode(true)
	//連接配接池
	DB.DB().SetMaxIdleConns(10)
	DB.DB().SetMaxOpenConns(100)
}
           

資料庫連接配接

sqlite3

使用

sqlite

資料庫來快速連接配接

package main

import (
	"encoding/json"
	"fmt"
	"github.com/jinzhu/gorm"
	//這裡是gorm封裝的資料庫驅動
	_ "github.com/jinzhu/gorm/dialects/sqlite"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

func initSqlite3Db() {
	// 初始化
	var err error
	DB, err = gorm.Open("sqlite3", "test.db")
	// 檢查錯誤
	if err != nil {
		panic(err)
	}
}
           

mysql

func initMysqlDb() {
	// 初始化
	var err error
	DB, err = gorm.Open("mysql", "root:[email protected](127.0.0.1:3306)/testdb?charset=utf8")
	// 檢查錯誤
	if err != nil {
		panic(err)
	}
}
           

模型定義

tag:gorm

通過

tag

标記

gorm

來建立在資料庫中字段的限制和屬性配置

type User struct {
	Id         int64  `gorm:"primary_key;AUTO_INCREMENT"` //設定為primary_key主鍵,AUTO_INCREMENT自增
	UserId     string  `gorm:"index:idx_user_id;not null`     //設定普通索引,索引名稱為idx_user_id,not null不能為空
	UserName   string `gorm:"type:varchar(64);not null"`  //設定為varchar類型,長度為64,not null不能為空
	Age        int
	Phone      int       `gorm:"unique_index:uk_phone"` //設定唯一索引,索引名為uk_phone
	CreateTime time.Time `gorm:"not null"`
	UpdateTime time.Time `gorm:"not null"`
	UserRemark string    `gorm:"column:remark;default:'預設'"`
	IgnoreMe   int       `gorm:"-"` // 忽略這個字段
}
           
gorm定義 資料庫字段限制

gorm:"primary_key"

字段設定為主鍵

gorm:"AUTO_INCREMENT"

字段設定為自增

gorm:"size:20

字段長度設定為20

gorm:"index:idx_user_id

字段設定普通索引,名稱為idx_user_id

gorm:"not null

設定字段為非空

gorm:"type:varchar(64)"

設定字段為varchar類型,長度為64

gorm:"column:remark"

設定資料庫字段名為remark

gorm:"-"

忽略此字段,不在表中建立該字段

gorm:"default:'預設'"

設定字段的預設值
注意:不一定是最全的示例,陸續補充

表名

gorm

中,表名建立是以複數存在的,當建立表之前設定以下選項可以禁用表名複數

// 全局禁用表名複數
	// 如果設定為true,`User`的預設表名為`user`,使用`TableName`設定的表名不受影響
	DB.SingularTable(true)
           

或者在每個

model

結構體中聲明

TableName

方法,若存在該方法

gorm

會優先采用這種方式來配置表名

type User struct {} // 預設表名是`users`

// 設定User的表名為`user`
func (User) TableName() string {
  return "user"
}
           

基礎操作

新增

NewRecord主鍵檢查 & Create

//Create
func CreateUser() {
	user := &User{
		UserId:     "USER_001",
		UserName:   "zhangsan",
		Age:        10,
		Phone:      19901020305,
		CreateTime: time.Now(),
		UpdateTime: time.Now(),
		UserRemark: "備注",
	}
	//1、NewRecord會檢查主鍵是否存在
	boolBefore := DB.NewRecord(user)
	//主鍵存在傳回true;否則傳回false	這裡傳回true,因為主鍵ID是自動生成這裡ID是空的不存在
	fmt.Println("boolBefore => ", boolBefore)

	//2、Create
	err := DB.Create(user).Error
	if err != nil {
		fmt.Printf("CreateUser error %v \n", err)
	}
	//這裡會傳回主鍵ID
	fmt.Println("user => ", toJson(user))

	//3、NewRecord會檢查主鍵是否存在
	boolAfter := DB.NewRecord(user)
	//主鍵存在傳回true;否則傳回false  這裡傳回false,因為主鍵ID已經存在了
	fmt.Println("boolAfter => ", boolAfter)
	//這裡有完整資料傳回,包含主鍵ID
	fmt.Println("user => ", toJson(user))
}
           

列印日志如下:

boolBefore =>  true
user =>  {"Id":1,"UserId":"USER_001","UserName":"zhangsan","Age":10,"Phone":19901020301,"CreateTime":"2021-06-01T13:33:02.237956+08:00","UpdateTime":"2021-06-01T13:33:02.237956+08:00","UserRemark":"預設","IgnoreMe":0}
boolAfter =>  false
user =>  {"Id":1,"UserId":"USER_001","UserName":"zhangsan","Age":10,"Phone":19901020301,"CreateTime":"2021-06-01T13:33:02.237956+08:00","UpdateTime":"2021-06-01T13:33:02.237956+08:00","UserRemark":"預設","IgnoreMe":0}
           

表資料如下:

sqlite> select * from user;
1|USER_001|zhangsan|10|19901020301|2021-06-01 13:33:02.237956+08:00|2021-06-01 13:33:02.237956+08:00|預設
           

查詢

First:查詢第一條記錄

func FindFirstRecord()  {
	result := &User{}
	// SELECT * FROM user ORDER BY id LIMIT 1;
	err:=DB.First(result).Error
	
	if err != nil {
		fmt.Printf("FindFirstRecord error %v",err)
		return
	}
	fmt.Printf(toJson(result))
}
           

日志輸出如下:

Last:查詢最後一條記錄

func FindLastRecord()  {
	result := &User{}
	// SELECT * FROM users ORDER BY id DESC LIMIT 1
	err:=DB.Last(result).Error

	if err != nil {
		fmt.Printf("FindLastRecord error %v",err)
	    return
	}
	fmt.Printf(toJson(result))
}
           

日志輸出如下:

First(… , pk):根據主鍵查詢記錄

func FindRecordByPK()  {
	result := &User{}
	// SELECT * FROM users WHERE id = 2
	err:=DB.First(&User{},2).Find(result).Error

	if err != nil {
		fmt.Printf("FindRecordByPK error %v",err)
		return
	}
	fmt.Printf(toJson(result))
}
           

日志輸出如下:

Where(…) 條件查詢條件

關于條件查詢相對來說比較簡單,這裡不贅述可以參考 https://jasperxu.com/gorm-zh/crud.html#q

FirstOrInit() & Attrs() & Assign()

擷取第一個比對的記錄,或者使用給定的條件初始化一個新的記錄傳回。

func FirstOrInit() {
	user := &User{
		UserId:     "USER_001",
		UserName:   "User",
		Age:        10,
		Phone:      19901020305,
		CreateTime: time.Now(),
		UpdateTime: time.Now(),
	}
	// 查詢 SELECT * FROM users WHERE user_name="guanjian" 該記錄存在
	err := DB.Attrs(&User{UserName: "attrs"}).
		// 這裡無論是否找到都強制指派
		Assign(&User{UserRemark: "強制修改"}).
		// 沒有這條記錄則填補,初始化的填充邏輯是 Attrs > Where (沒有Attrs則使用Where條件)
		// 如果有則傳回查到的第一條比對的記錄
		FirstOrInit(user, User{UserName: "guanjian"}).Error

	if err != nil {
		fmt.Printf("FirstOrInit error %v", err)
		return
	}
	fmt.Printf(toJson(user))
}
           

FirstOrInit

方法如果查不到結果不會落庫,隻是會初始化

FirstOrInit(obj)

中的

obj

對象的值,且将Where條件中的值一并初始化出來

FirstOrCreate() & Attrs() & Assign()

FirstOrInit

類似,不同的是它會進行落庫操作。擷取第一個比對的記錄,或者使用給定的條件進行落庫建立。

func FirstOrCreate() {
	user := &User{
		UserId:     "USER_001",
		UserName:   "guanjian",
		Age:        10,
		Phone:      19901020310,
		CreateTime: time.Now(),
		UpdateTime: time.Now(),
	}
	// 查詢 SELECT * FROM users WHERE id = 10
	// 沒有這條記錄則按照user來初始化user進行填補
	// 如果有則傳回查到的第一條比對的記錄
	err := DB.FirstOrCreate(user, User{Id: 10}).Error

	if err != nil {
		fmt.Printf("FirstOrInit error %v", err)
		return
	}
	fmt.Printf(toJson(user))
}
           

FirstOrCreate

方法如果查不到結果會落庫,也會初始化

FirstOrCreate(obj)

中的

obj

對象的值,且将Where條件中的值一并初始化出來

Select查詢字段定義

這裡隻查詢

user_name,age

字段傳回,其餘字段不會查詢到值傳回

func Select() {
	user := &User{}
	err := DB.Select("user_name,age").Find(user).Error

	if err != nil {
		fmt.Printf("Select error %v", err)
		return
	}
	fmt.Println("隻查詢user_name,age字段", toJson(user))
}
           

Scan

func Scan() {
	user := &User{}
	err := DB.Table("user").Select([]string{"user_name","age"}).Scan(user).Error
	if err != nil {
		fmt.Printf("Scan error %v", err)
		return
	}
	fmt.Println("user => ", toJson(user))
}
           

Scopes 動态條件添加

實作方法形如

func(db *gorm.DB) * gorm.DB

則可以配合方法

Scopes

進行動态條件的添加,編碼更清晰友好

//這裡比對create_time查詢條件
func WithCreateTimeLessThanNow(db *gorm.DB) *gorm.DB {
	return db.Where("create_time < ?", time.Now())
}

//這裡比對age查詢條件
func WithAgeGreaterThan0(db *gorm.DB) *gorm.DB {
	return db.Where("age > ?", 0)
}

func Scopes() {
	user := &User{}
	//查詢條件動态拼接
	err := DB.Scopes(WithAgeGreaterThan0,WithCreateTimeLessThanNow).Find(user).Error
	if err != nil {
		fmt.Printf("Scopes error %v", err)
		return
	}
	fmt.Println("user => ", toJson(user))
}
           

修改

Save

儲存已存在的資料,觸發更新邏輯(update);儲存不存在的資料,直接插入(insert)

//儲存存在的資料,觸發更新邏輯
func SaveExist() {
	user := &User{
		Id: 1,
	}
	//查詢id=9999的資料,是不存在的
	user.UserName = "saveNewUserName"
	//按照主鍵做Update操作
	err := DB.Save(user).Error
	if err != nil {
		fmt.Printf("Save error %v", err)
		return
	}
	fmt.Println("save user => ", toJson(user))
}
//儲存不存在的資料,直接插入
func SaveNoExist() {
	user := &User{
		Id: 9999,
		Phone: 19902029492,
	}
	//查詢id=9999的資料,是不存在的
	user.UserName = "saveNewUserName"
	//按照主鍵做Update操作
	err := DB.Save(user).Error
	if err != nil {
		fmt.Printf("Save error %v", err)
		return
	}
	fmt.Println("save user => ", toJson(user))
}
           

Update & Updates & Omit

func Update() {
	user := &User{
		Id: 9999,
	}

	err := DB.Model(user).Update("user_name", "this is 9999").Error
	if err != nil {
		fmt.Printf("Update error %v", err)
		return
	}
}
           

更新字段效果如下:

[Golang]Gorm使用彙總資料安裝資料庫配置資料庫連接配接模型定義基礎操作回調使用(Callback)異常處理事務日志參考

使用

Updates

方法可以批量更新多個字段

func Updates() {
	//查詢字段
	whereUser := &User{
		Id: 9999,
	}
	//批量更新字段
	updateUser := &User{
		UserName: "This is Updates",
		Age: 9292,
		UserRemark: "Updates",
	}

	err := DB.Model(whereUser).Updates(updateUser).Error
	if err != nil {
		fmt.Printf("Updates error %v", err)
		return
	}
}
           

批量更新字段效果如下:

[Golang]Gorm使用彙總資料安裝資料庫配置資料庫連接配接模型定義基礎操作回調使用(Callback)異常處理事務日志參考

使用

Omit

方法可以忽略該字段的更新

func UpdateOmit() {
	//查詢字段
	whereUser := &User{
		Id: 1,
	}
	//批量更新字段
	updateUser := &User{
		UserName:   "This is Updates",
		Age:        6666,
		UserRemark: "1 -> Update",
	}

	err := DB.Model(whereUser).
		//忽略user_name字段的更新
		Omit("user_name").
		Updates(updateUser).Error
	if err != nil {
		fmt.Printf("Updates error %v", err)
		return
	}
}
           
[Golang]Gorm使用彙總資料安裝資料庫配置資料庫連接配接模型定義基礎操作回調使用(Callback)異常處理事務日志參考
以上更新操作将執行模型的

BeforeUpdate, AfterUpdate

方法,更新其UpdatedAt時間戳,在更新時儲存它的Associations,如果不想調用它們,可以使用UpdateColumn, UpdateColumns

非零值更新問題 為了避免誤操作或髒資料對全表進行更新,需要配置

WHERE

必填條件進行限制

使用

RowsAffected

可以傳回目前更新的行數

func RowsAffected() {
	//查詢字段
	whereUser := &User{
		Id: 9999,
	}
	//批量更新字段
	updateUser := &User{
		UserName:   "This is Updates",
		Age:        9292,
		UserRemark: "Updates",
	}
	//RowsAffected傳回目前更新的行數
	count := DB.Model(whereUser).Updates(updateUser).RowsAffected
	fmt.Printf("Updates count %v", count)
}
           

删除

一般使用資料庫邏輯删除,這部分可參考 https://jasperxu.com/gorm-zh/crud.html#d

回調使用(Callback)

在建立,更新,查詢,删除時将被調用,如果任何回調傳回錯誤,gorm将停止未來操作并復原所有更改。

Callback預設定義

gorm

源碼中可以看到預設的

callback

注冊,這裡着重關注

callback_create.go、callback_delete.go、callback_query.go、callback_update.go

即可,其實整個

gorm

的執行流程都是基于

callback

來實作整個執行流程的。

[Golang]Gorm使用彙總資料安裝資料庫配置資料庫連接配接模型定義基礎操作回調使用(Callback)異常處理事務日志參考

下面舉例

callback_create.go

檔案的

init()

方法,按照從上到下的順序,注冊了

callback

函數,每個

callbackName

(如:gorm:create)都可以作為回調的插入點使用,配合

Before、After

可以實作前置插入、後置插入等,其他

callback

類似這裡不再贅述

// Define callbacks for creating
func init() {
	DefaultCallback.Create().Register("gorm:begin_transaction", beginTransactionCallback)
	DefaultCallback.Create().Register("gorm:before_create", beforeCreateCallback)
	DefaultCallback.Create().Register("gorm:save_before_associations", saveBeforeAssociationsCallback)
	DefaultCallback.Create().Register("gorm:update_time_stamp", updateTimeStampForCreateCallback)
	DefaultCallback.Create().Register("gorm:create", createCallback)
	DefaultCallback.Create().Register("gorm:force_reload_after_create", forceReloadAfterCreateCallback)
	DefaultCallback.Create().Register("gorm:save_after_associations", saveAfterAssociationsCallback)
	DefaultCallback.Create().Register("gorm:after_create", afterCreateCallback)
	DefaultCallback.Create().Register("gorm:commit_or_rollback_transaction", commitOrRollbackTransactionCallback)
}
           

Callback事務保證

檢視

gorm

源碼,諸如CRUD的方法如

Create()、Update()、Find()、Delete()

等内部都進行了callback方法等調用,callback内部代碼如下:

func (scope *Scope) callCallbacks(funcs []*func(s *Scope)) *Scope {
	//這裡進行了異常捕獲
	defer func() {
		if err := recover(); err != nil {
			if db, ok := scope.db.db.(sqlTx); ok {
				//異常則對事務進行復原
				db.Rollback()
			}
			panic(err)
		}
	}()
	for _, f := range funcs {
		(*f)(scope)
		if scope.skipLeft {
			break
		}
	}
	return scope
}
           

下面來看一個

callback

中産生panic異常,資料庫復原的示例,如下:

func Callback() {
	//注冊在gorm:create節點之後執行函數
	DB.Callback().Create().
		//在CreateUser()函數方法,即DB執行之後再執行
		After("gorm:create").
		Register("gorm:AfterCreate", func(scope *gorm.Scope) {
		fmt.Println("AfterCreate")
		//這裡抛出異常,會被callCallbacks的recover方法捕獲
		panic("AfterCreate error")
	})
	//由于上面callback已經panic,這裡會復原
	CreateUser()
}
           

異常處理

可以對

RecordNotFound()、Error、GetErrors()

來處理記錄不存在、異常、多個異常等情況

func Error() {
	//RecordNotFound
	bool := DB.First(&User{Id: 9999}).RecordNotFound()
	fmt.Println("RecordNotFound => ", bool)

	//Error
	err := DB.Create(&User{}).Error
	fmt.Println("err => ", err)

	errs := DB.Create(&User{}).GetErrors()
	fmt.Println("errs => ", errs)
}
           

事務

注意,一旦你在一個事務中,使用

tx

作為資料庫句柄,如果使用

gorm.DB

操作,是不受事務管控的,這裡需要注意!

func Tx() {
	//開啟事務
	tx := DB.Begin()

	//執行方法1  會執行成功
	err1 := tx.Create(&User{
		UserId:     "USER_002",
		UserName:   "zhangsan",
		Age:        0,
		Phone:      17701020304,
		CreateTime: time.Now(),
		UpdateTime: time.Now(),
		UserRemark: "",
		IgnoreMe:   0,
	}).Error
	if err1 != nil {
		tx.Rollback()
		fmt.Println("=== Create 1 error ===")
	}
	//執行方法2 會執行失敗,有Phone唯一主鍵限制
	err2 := tx.Create(&User{
		UserId:     "USER_002",
		UserName:   "zhangsan",
		Age:        0,
		Phone:      17701020304,
		CreateTime: time.Now(),
		UpdateTime: time.Now(),
		UserRemark: "",
		IgnoreMe:   0,
	}).Error
	if err2 != nil {
		tx.Rollback()
		fmt.Println("=== Create 2 error ===")
	}
	tx.Commit()
}
           

日志

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

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

// 調試單個操作,顯示此操作的詳細日志
db.Debug().Where("name = ?", "jinzhu").First(&User{})
           

參考

https://jasperxu.com/gorm-zh/