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时钩子会失效,批量操作会导致钩子无效,另外一些特殊的方法也会,所以重要数据千万不要偷懒用钩子操作
GORM自定义Gorm.Model实现自动添加时间戳的更多相关文章
- 【Eclipse】如何在Eclipse中如何自动添加注释和自定义注释风格
背景简介 丰富的注释和良好的代码规范,对于代码的阅读性和可维护性起着至关重要的作用.几乎每个公司对这的要求还是比较严格的,往往会形成自己的一套编码规范.但是再实施过程中,如果全靠手动完成,不仅效率低下 ...
- 如何在Eclipse中如何自动添加注释和自定义注释风格
1. 如何自动添加注释 可通过如下三种方法自动添加注释: (1)输入“/**”并回车. (2)用快捷键 Alt+Shift+J(先选中某个方法.类名或变量名). (3)在右键菜单中选择“Source ...
- WordPress发布文章/页面时自动添加默认的自定义字段
如果你每篇文章或页面都需要插入同一个自定义字段和值,可以考虑在WordPress发布文章/页面时,自动添加默认的自定义字段.将下面的代码添加到当前主题的 functions.php 即可: 1 2 3 ...
- 打造强大的BaseModel(2):让Model实现自动映射,将字典转化成Model
打造强大的BaseModel(1):让Model自我描述 这篇文章将讲述Model一项更高级也最常用的功能,让Model实现自动映射–将字典转化成Model(所有代码全由Swift实现) 将JSON转 ...
- ffmpeg为视频添加时间戳 - 手动编译ffmpeg
FFMPEG给视频加时间戳水印 项目中需要给视频添加时间戳,理所当然最好用的办法是ffmpeg.在找到正确的做法前,还被网上的答案timecode给水了一下(水的不轻,在这里转了2天),大概是这样写的 ...
- 前端自动化工具gulp自动添加版本号
之前,我介绍了学习安装并配置前端自动化工具Gulp,觉得gulp确实比grunt的配置简单很多,于是我决定再深入学习一下gulp,就去网上查了资料,发现gulp还可以自动添加版本号,这个功能就为我平时 ...
- Android中仿淘宝首页顶部滚动自定义HorizontalScrollView定时水平自动切换图片
Android中仿淘宝首页顶部滚动自定义HorizontalScrollView定时水平自动切换图片 自定义ADPager 自定义水平滚动的ScrollView效仿ViewPager 当遇到要在Vie ...
- VS 自动添加注释
现在大多数公司都规定程序员在程序文件的头部加上版权信息,这样每个人写的文件都可以区分开来,如果某个文件出现问题就可以快速的找到文件的创建人,用最短的时间来解决问题,常常是以下格式: //======= ...
- XsdGen:通过自定义Attribute与反射自动生成XSD
前言 系统之间的数据交互往往需要事先定义一些契约,在WCF中我们需要先编写XSD文件,然后通过自动代码生成工具自动生成C#对象.对于刚刚接触契约的人来说,掌握XMLSpy之类的软件之后确实比手写XML ...
- 为js和css文件自动添加版本号
web应用必然要面对缓存问题,无论前台后台都会涉足缓存.特别是对于前端而言,缓存利用的是否得当直接关系到应用的性能. 通常情况下,我们会倾向于使用缓存,因为缓存一方面可以减少网络开销,一方面可以减轻服 ...
随机推荐
- 让优惠再续一年!SHPC 老客专享
最近云筏君经常收到自家小伙伴发来的关于产品活动的关心慰问,掐指一算,哦,原来是一年一度大家喜闻乐见的剁手节(学名"双十一")马上要来了! 大家都知道,云筏家的产品向来主打高性价比, ...
- Java:错误:不支持发行版本5
#解决方案1 1.点击File--Project Structure 2.点击Project 3.查看jdk版本是否和安装的一样 4.点击Modules 查看版本 5.点击Preferences--B ...
- 前端Vue自定义顶部搜索框 热门搜索 历史搜索 用于搜索跳转使用
前端Vue自定义顶部搜索框 热门搜索 历史搜索 用于搜索跳转使用, 下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=13128 效 ...
- 7. RESTful
1. RESTful简介 REST:Representational State Transfer,表现层资源状态转移. ①资源 资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源 ...
- TransformersandNLPforVideoUnderstanding
目录 1. 引言 2. 技术原理及概念 3. 实现步骤与流程 4. 应用示例与代码实现讲解 <Transformers and NLP for Video Understanding> 1 ...
- 使用ansible-app2k8s管理和部署服务到 kubernetes
ansible-app2k8s #1 介绍 使用 ansible 管理和部署服务到 kubernetes 适用于项目容器化,多套 k8s 环境的管理,可结合CICD工具做DevOps 来自于项目实践, ...
- Java 访问控制权限修饰符
1.访问控制权限修饰符来控制元素的访问范围 2.访问控制权限修饰符包括: public 表示公开的,任何位置都可以可以访问 protected 同包,子类 缺省 同包 private 表示私有的,只能 ...
- Thinkphp6 连接达梦数据库
Thinkphp6 连接达梦数据库 这里使用 IDEA phpEnv PHP7.3 Thinkphp6 桌面操作系统:Windows11 虚拟机:VMware 服务器操作系统:银河麒麟 在虚拟机操作与 ...
- 字符串加密DES
提前关于加密的方式,我目前知道的有MD5,DES等等.今天写一下使用DES的代码,方便下次使用. package mocha.framework.hrbase.rest.utils; import j ...
- Flutter系列文章-Flutter环境搭建和Dart基础
Flutter是Google推出的一个开源的.高性能的移动应用开发框架,可以用一套代码库开发Android和iOS应用.Dart则是Flutter所使用的编程语言.让我们来看看如何搭建Flutter开 ...