Gorm.Model.DeletedAt 变量类型分析

ChaunhewieTian · · 3873 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

以下介绍基于 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 <null> <null> <null> 0 instance_test_01 test des
xxxxxx6665967374125965312 <null> <null> <null> 0 instance_test_01 test des
xxxxxx6665967380304175104 <null> <null> <null> 0 instance_test_01 test des
xxxxxx6665967380643913728 <null> <null> <null> 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 虽然是 <null>,但是却 is not null
由于 gorm 所有的查询语句都会加入 `instances`.`deleted_at` IS NULL 句,因此所有的查询都会失败,得到 'record not found' 错误(gorm.ErrRecordNotFound

分析解析路径

  1. 当调用 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
}
  1. (*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
  1. 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.Fieldreflect.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


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

本文来自:简书

感谢作者:ChaunhewieTian

查看原文:Gorm.Model.DeletedAt 变量类型分析

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3873 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传