GORM自定义Gorm.Model实现自动添加时间戳
废话不说直接开始
官网(http://gorm.io)有给出一套默认的gorm.Model模型,定义如下
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,创建时间,更新时间,删除时间,当操作数据时会自动更改相应的时间,删除时会将删除改成软删除并添加删除时间。
为什么官网已经有了还要自己写一套呢?理由有二:
1.我在做的是项目重构,原有数据库里已经有了GUID格式的主键,和此模型冲突(虽然不知为啥但官方明确指出支持复合主键但不建议这样做)
2.三个时间字段存储的是时间,而原项目存储的时间戳
既然决定重写就得先搞懂他是如何运作的,源码里找到DB对象定义如下
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
(官方源码里没有注释)
好现在知道了这些钩子方法是通过回调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 初始化数据库连接(可在main()适当位置调用) */ 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时钩子会失效,批量操作会导致钩子无效,另外一些特殊的方法也会,所以重要数据千万不要偷懒用钩子操作