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应用必然要面对缓存问题,无论前台后台都会涉足缓存.特别是对于前端而言,缓存利用的是否得当直接关系到应用的性能. 通常情况下,我们会倾向于使用缓存,因为缓存一方面可以减少网络开销,一方面可以减轻服 ...
随机推荐
- docker部署gitlab CI/CD (二)终篇:部署gitlab runner和添加gitlab-ci.yml文件 终极踩坑版
关于gitlab部署的教程还好,有的看,但到了cicd环节,简直痛苦面具,教程虽多,但断断续续,先不说大部分都是只截取片段,让人云里雾里,不会的看不懂,懂的不需要看,根据步骤跑不起来不说,改了一堆,完 ...
- 魔力屏障 (magic) 题解
魔力屏障 (magic) [问题描述] 小 Z 生活在神奇的魔法大陆上.今天他的魔法老师给了它这样一个法阵作为它 的期末考试题目: 法阵由从左至右 n 道魔力屏障组成,每道屏障有一个临界值 a,如果它 ...
- 使用脚本收发 protobuf 协议数据
问题背景 最近做了一个 ipv6 相关的功能,发现使用 getifaddrs 获取的本地 ipv6 地址有可能不是真实的网络 ipv6 地址: 例如上图中通过 getifaddrs 获得了多个本地 i ...
- 解决element-ui下拉框数据过多,导致页面卡顿问题与本地分页功能实现
效果 前情提要: 最近使用element-ui开发的一个页面,在打开的时候占用cpu非常高,有时候都能达到90%↑.在调试时发现其中一个下拉框的接口返回2k↑的数据.本着有问题问百度的精神,看到主要的 ...
- S32DS学习日志:debug文件和烧录的.hex文件
工程导入之后先clean一下,重新编译生成的文件默认在Production文件下面,得重新设置 折腾半天用jlink烧录没反应,原来是这里错了. production下的文件是用来用来集成bootlo ...
- 自己动手实现rpc框架(一) 实现点对点的rpc通信
自己动手实现rpc框架(一) 实现点对点的rpc通信 1. 什么是rpc? RPC是远过程调用(Remote Procedure Call)的缩写形式,其区别于一个程序内部基本的过程调用(或者叫函数/ ...
- 聊天室(二)__ unipush 推送实现详细教程
一.推送作用 推送作用我就不废话了,能做推送的都知道作用,直接上干货. 二.unipush 快速开通 Dcloud 开发者实名认证注册账号,绑定对应的 app 信息. uni-push产品有2个入 ...
- 理解ffmpeg
ffmpeg是一个完整的.跨平台的音频和视频录制.转换和流媒体解决方案. 它的官网:https://ffmpeg.org/ 这里有一份中文的文档:https://ffmpeg.p2hp.com/ ff ...
- NodeJS使用npm安装vue脚手架
开发环境准备:Windows10.Windows11 NodeJS,安装官网最新LTS版即可 下载地址:https://nodejs.org/安装一路下一步,默认即可 ================ ...
- spring-boot-plus2.7.12版本重磅发布,三年磨一剑,兄弟们等久了,感谢你们的陪伴
Everyone can develop projects independently, quickly and efficiently! spring-boot-plus是一套集成spring bo ...