gorm软删除_Gorm.Model.DeletedAt 变量类型分析

本文介绍了Gorm在Golang中处理Mysql时的软删除功能,通过`gorm.Model`的`DeletedAt`字段实现。当`DeletedAt`不为null时,查询会失败,因为所有查询语句都包含`deleted_at` IS NULL条件。文章强调了`DeletedAt`使用`*time.Time`类型的必要性,避免因误改为`time.Time`导致的查询错误。
部署运行你感兴趣的模型镜像

以下介绍基于 Golang 语言的操作

Gorm 介绍

Gorm 是处理 Mysql 的一个工具。默认使用 struct `Name` 对应的 `Name`s 作为表名,同时 struct 参数名,作为列名。

# 可以通过修改 TableName() 更改 struct 默认的表名

func (i *Instance) TableName() string {

return "instance" # 默认 Instance 结构对应 instances 数据库表

}

Gorm 利用 gorm.Model 实现软删除,其中通过 deleted_at 来实现,当删除的时候,仅仅是更新 deleted_at 字段,而不是直接删除。

注意:因此会引发一个软删除的问题:就是主键ID不会释放,如果插入一个ID和一个软删除掉的记录ID相同的数据,则会失败。可以在删除前,update一下 ID,使之和在线数据的 ID 规格不同。

gorm.Model

gorm/models.go

type Model struct {

ID uint `gorm:"primary_key"`

CreatedAt time.Time

UpdatedAt time.Time

DeletedAt *time.Time `sql:"index"`

}

注意:CreatedAt 和 UpdatedAt 的 type 是 time.Time,而 DeletedAt 的 type 是 *time.Time

instance.go

type Instance struct {

gorm.Model

Name string `gorm:"type:varchar(255)"`

Description string `gorm:"type:varchar(255)"`

DeletedAt 为何用 *time.Time

如果不慎修改了 DeletedAt 字段的 type(*time.Time -> time.Time),那么会导致如下问题

假设数据库表 `instances` 通过 gorm 代码 db.Create(instance).Error 插入 4 条数据,数据库查看数据如下:

id

created_at

updated_at

deleted_at

--

name

description

xxxxxx6665967373685563392

0

instance_test_01

test des

xxxxxx6665967374125965312

0

instance_test_01

test des

xxxxxx6665967380304175104

0

instance_test_01

test des

xxxxxx6665967380643913728

0

instance_test_01

test des

由于 deleted_at 字段代码中为 time.Time,会导致查询语句的以下结果:

# 查询为空,此句为 代码 db.Where("name = ? ", name).First(row).Error 执行,gorm 所生成的 SQL 语句

SELECT * FROM `instances` WHERE `instances`.`deleted_at` IS NULL AND (((instances.name = 'instance_test_01'))) ORDER BY `instances`.`name`;

# 查询得到四条语句,如上表

SELECT * FROM `instances` WHERE (((instances.name = 'instance_test_01'))) ORDER BY `instances`.`name`;

# 查询为空

SELECT * FROM `instances` WHERE `instances`.`deleted_at` is null;

# 查询得到四条语句,如上表

SELECT * FROM `instances` WHERE `instances`.`deleted_at` is not null;

即,deleted_at 虽然是 ,但是却 is not null

由于 gorm 所有的查询语句都会加入 `instances`.`deleted_at` IS NULL 句,因此所有的查询都会失败,得到 'record not found' 错误(gorm.ErrRecordNotFound)

分析解析路径

当调用 db.create(&instance{}) 时,gorm 会依次调用 callback 来进行 create

// Create insert the value into database

func (s *DB) Create(value interface{}) *DB {

scope := s.NewScope(value)

return scope.callCallbacks(s.parent.callbacks.creates).db

}

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

}

在 (*f)(scope) 处依次调用 callback 函数:

gorm.beginTransactionCallback

用户自定义注册的callback,比如:db.Callback().Create().Before("gorm:before_create").Register("id:generate", idGenerateCallback)

gorm.beforeCreateCallback

gorm.saveBeforeAssociationsCallback

gorm.updateTimeStampForCreateCallback

gorm.createCallback

gorm.forceReloadAfterCreateCallback

gorm.saveAfterAssociationsCallback

gorm.afterCreateCallback

gorm.commitOrRollbackTransactionCallback

在 gorm.createCallback 中,进行参数的获取并写入数据库

// createCallback the callback used to insert data into database

func createCallback(scope *Scope) {

if !scope.HasError() {

defer scope.trace(NowFunc())

var (

columns, placeholders []string

blankColumnsWithDefaultValue []string

)

for _, field := range scope.Fields() {

if scope.changeableField(field) {

...

else if !field.IsPrimaryKey || !field.IsBlank {

columns = append(columns, scope.Quote(field.DBName))

placeholders = append(placeholders, scope.AddToVars(field.Field.Interface()))

}

}

}

}

}

...

}

在 placeholders = append(placeholders, scope.AddToVars(foreignField.Field.Interface())) 句中,foreignField.Field 是 reflect.Value,调用 Interface() 得到该 Value 对应的数据,并加入 scope.SQLVars

执行完成 for 循环,得到如下变量:

columns

placeholders

scope.SQLVars(Type)

scope.SQLVars(Value)

id

$$$

{interface{}|string}

xxxxxx6666161042736750592

created_at

$$$

{interface{}|time.Time}

updated_at

$$$

{interface{}|time.Time}

deleted_at

$$$

{interface{}|time.Time}

对比 *time.Time ⬇️

{interface{}| nil}

--

$$$

{interface{}|int64}

0

name

$$$

{interface{}|string}

instance_test_01

description

$$$

{interface{}|string}

test des

综上:虽然数据库查询 deleted_at 字段为空,但是写入的时候,并不是写入 nil,而是写入了空数据,故 deleted_at IS NULL 判断失败

额外TIP:

当 Instance 结构体引用其他结构体时,如果是可能为Null的,都要用指针,否则不好判断是不是真的查到的这条记录。

比如 Instance 加入一个 Port 结构体,如果这个 Instance 没有 Port,那在查询的时候,这个 Port 里面的所有值都是默认值,就需要通过 instance.Port.Id != "" 来判断是不是查询到 Port

有疑问加站长微信联系(非本文作者)

您可能感兴趣的与本文相关的镜像

EmotiVoice

EmotiVoice

AI应用

EmotiVoice是由网易有道AI算法团队开源的一块国产TTS语音合成引擎,支持中英文双语,包含2000多种不同的音色,以及特色的情感合成功能,支持合成包含快乐、兴奋、悲伤、愤怒等广泛情感的语音。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值