廢話不說直接開始mysql
官網(http://gorm.io)有給出一套默認的gorm.Model模型,定義以下git
package gorm import "time" // Model base model definition, including fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`, which could be embedded in your models // type User struct { // gorm.Model // } type Model struct { ID uint `gorm:"primary_key"` CreatedAt time.Time UpdatedAt time.Time DeletedAt *time.Time `sql:"index"` }
包含四個屬性,ID,建立時間,更新時間,刪除時間,當操做數據時會自動更改相應的時間,刪除時會將刪除改爲軟刪除並添加刪除時間。github
爲何官網已經有了還要本身寫一套呢?理由有三:sql
1.我在作的是項目重構,原有數據庫裏已經有了GUID格式的主鍵,和此模型衝突(雖然不知爲啥但官方明確指出支持複合主鍵但不建議這樣作)數據庫
2.三個時間字段存儲的是時間,而原項目存儲的時間戳數組
3.裝逼,顯擺,閒的沒事app
既然決定重寫就得先搞懂他是如何運做的,源碼裏找到DB對象定義以下tcp
db相關信息,每次綁定不一樣的value,操做對象例如product{} type DB struct { Value interface{} Error error RowsAffected int64 // single db db SQLCommon //原生db.sql對象,包含query相關的原生方法 blockGlobalUpdate bool logMode int logger logger search *search //保存搜索的條件where, limit, group,好比調用db.clone()時,會指定search values map[string]interface{} // global db parent *DB callbacks *Callback //當前sql綁定的函數調用鏈 dialect Dialect //不一樣數據庫適配註冊sql.db singularTable bool } // 保存當前sql執行相關信息 type Scope struct { Search *search // 檢索條件 Value interface{} SQL string //sql SQLVars []interface{} db *DB //sql.db instanceID string primaryKeyField *Field skipLeft bool fields *[]*Field //字段 selectAttrs *[]string } // 保存各類操做須要執行的調用鏈,例如create函數,須要調用creates數組中全部的函數 type Callback struct { creates []*func(scope *Scope) updates []*func(scope *Scope) deletes []*func(scope *Scope) queries []*func(scope *Scope) rowQueries []*func(scope *Scope) processors []*CallbackProcessor }
代碼引用自: https://blog.csdn.net/qq_17612199/article/details/79437795函數
(官方源碼裏沒有註釋)flex
好如今知道了這些鉤子方法是經過回調DB對象的Callback實現的而Callback下又是一個個的切片說明回調並不僅是一個,找到官方的Creates回調
// 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) }
找到‘update_time_stamp’對應的方法‘updateTimeStampForCreateCallback’
// updateTimeStampForCreateCallback will set `CreatedAt`, `UpdatedAt` when creating func updateTimeStampForCreateCallback(scope *Scope) { if !scope.HasError() { now := NowFunc() if createdAtField, ok := scope.FieldByName("CreatedAt"); ok { if createdAtField.IsBlank { createdAtField.Set(now) } } if updatedAtField, ok := scope.FieldByName("UpdatedAt"); ok { if updatedAtField.IsBlank { updatedAtField.Set(now) } } } }
好了,到了這一步相信各位已經知道該如何重寫了,須要注意的是上邊DefaultCallback.Create()用到的是Register()方法,Create()定義以下
// Register a new callback, refer `Callbacks.Create` func (cp *CallbackProcessor) Register(callbackName string, callback func(scope *Scope)) { if cp.kind == "row_query" { if cp.before == "" && cp.after == "" && callbackName != "gorm:row_query" { log.Printf("Registing RowQuery callback %v without specify order with Before(), After(), applying Before('gorm:row_query') by default for compatibility...\n", callbackName) cp.before = "gorm:row_query" } } cp.name = callbackName cp.processor = &callback cp.parent.processors = append(cp.parent.processors, cp) cp.parent.reorder() }
既然已經註冊過了那重寫的話就不能用Register了,源碼中找到一個Replace方法用於替換原有的回調,其定義以下
// Replace a registered callback with new callback // db.Callback().Create().Replace("gorm:update_time_stamp_when_create", func(*Scope) { // scope.SetColumn("Created", now) // scope.SetColumn("Updated", now) // }) func (cp *CallbackProcessor) Replace(callbackName string, callback func(scope *Scope)) { log.Printf("[info] replacing callback `%v` from %v\n", callbackName, fileWithLineNum()) cp.name = callbackName cp.processor = &callback cp.replace = true cp.parent.processors = append(cp.parent.processors, cp) cp.parent.reorder() }
好了,該掌握的都掌握瞭如今開始實際搞一下試一試
項目中model文件夾用於存放映射數據庫模型,新建changemodel.go文件
package model type ChangeModel struct{ CreatedTime int32 UpdatedTime int32 DeletedTime int32 }
在須要用到事件記錄的模型裏引入ChangeModel如
package model //User type User struct{ ChangeModel UserID string `gorm:"primary_key;size:36"` Name string `gorm:"size:20"` PassWord string `gorm:"size:40"` }
好了到此爲止自定義的公共字段已經弄好了,接下來是最重要的重寫回調並注入
打開本身的數據庫鏈接文件
package mysqltools import ( "fmt" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "log" "sync" "time" ) type MysqlConnectiPool struct { } var instance *MysqlConnectiPool var once sync.Once var db *gorm.DB var err_db error func GetInstance() *MysqlConnectiPool { once.Do(func() { instance = &MysqlConnectiPool{} }) return instance } /* * @fuc 初始化數據庫鏈接(可在mail()適當位置調用) */ func (m *MysqlConnectiPool) InitDataPool() (issucc bool) { db, err_db = gorm.Open("mysql", "name:password@tcp(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Local") fmt.Println(err_db) if err_db != nil { log.Fatal(err_db) return false } db.DB().SetMaxIdleConns(10) db.DB().SetMaxOpenConns(100) db.DB().SetConnMaxLifetime(time.Hour) db.Callback().Create().Replace("gorm:update_time_stamp",updateTimeStampForCreateCallback) db.Callback().Update().Replace("gorm:update_time_stamp",updateTimeStampForUpdateCallback) db.Callback().Delete().Replace("gorm:delete", deleteCallback) return true } func (m *MysqlConnectiPool) GetMysqlDB() (db_con *gorm.DB) { return db } // // 註冊新建鉤子在持久化以前 func updateTimeStampForCreateCallback(scope *gorm.Scope) { if !scope.HasError() { nowTime := time.Now().Unix() if createTimeField, ok := scope.FieldByName("CreatedTime"); ok { if createTimeField.IsBlank { createTimeField.Set(nowTime) } } if modifyTimeField, ok := scope.FieldByName("UpdatedTime"); ok { if modifyTimeField.IsBlank { modifyTimeField.Set(nowTime) } } } } // 註冊更新鉤子在持久化以前 func updateTimeStampForUpdateCallback(scope *gorm.Scope) { if _, ok := scope.Get("gorm:update_column"); !ok { scope.SetColumn("UpdatedTime", time.Now().Unix()) } } // 註冊刪除鉤子在刪除以前 func deleteCallback(scope *gorm.Scope) { if !scope.HasError() { var extraOption string if str, ok := scope.Get("gorm:delete_option"); ok { extraOption = fmt.Sprint(str) } deletedOnField, hasDeletedOnField := scope.FieldByName("DeletedTime") if !scope.Search.Unscoped && hasDeletedOnField { scope.Raw(fmt.Sprintf( "UPDATE %v SET %v=%v%v%v", scope.QuotedTableName(), scope.Quote(deletedOnField.DBName), scope.AddToVars(time.Now().Unix()), addExtraSpaceIfExist(scope.CombinedConditionSql()), addExtraSpaceIfExist(extraOption), )).Exec() } else { scope.Raw(fmt.Sprintf( "DELETE FROM %v%v%v", scope.QuotedTableName(), addExtraSpaceIfExist(scope.CombinedConditionSql()), addExtraSpaceIfExist(extraOption), )).Exec() } } } func addExtraSpaceIfExist(str string) string { if str != "" { return " " + str } return "" }
能夠看到三個對應的回調已經寫好並替換掉默認的鉤子回調
db.Callback().Create().Replace("gorm:update_time_stamp",updateTimeStampForCreateCallback) db.Callback().Update().Replace("gorm:update_time_stamp",updateTimeStampForUpdateCallback) db.Callback().Delete().Replace("gorm:delete", deleteCallback)
注意
鉤子並非萬能的,當使用Raw()寫原生SQL時鉤子會失效,批量操做會致使鉤子無效,另一些特殊的方法也會,因此重要數據千萬不要偷懶用鉤子操做