以下介绍基于 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

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

gorm软删除_Gorm.Model.DeletedAt 变量类型分析相关推荐

  1. gorm软删除_gorm的简单使用和注意事项

    Gorm当前支持MySql, PostgreSql, Sqlite等主流数据库 1.安装 首先安装数据库驱动go get github.com/go-sql-driver/mysql 然后安装gorm ...

  2. gorm软删除_GORM中文文档-Go语言中文社区

    入门指南 GORM是类似Django ORM,对开发人员友好的 Golang ORM 库. 概览 全特性 ORM (几乎包含所有特性) 模型关联 (一对一, 一对多,一对多(反向), 多对多, 多态关 ...

  3. gorm软删除_gorm踩坑:软删除与某个字段的唯一性

    有一个user_infos表,用户名唯一.我在model定义user_name的时候已经使用gorm的tag标记为unique_index.类似如下: type UserInfo struct { I ...

  4. gorm软删除_gorm 的预加载怎么才能关掉软删除-问答-阿里云开发者社区-阿里云

    gorm 的预加载怎么才能关掉软删除 type CourseType struct { gorm.Model TypeName string CreatedAt time.Time UpdatedAt ...

  5. gorm 软删除deleted_at导致索引失效

    如果使用gorm的时间戳设计,由于它的deleted_at字段允许为空,唯一索引加上这个字段后,唯一索引会失效. 第一种是像gorm那样,使用时间戳来标识已删除.不同的是,不使用IS NULL来判断未 ...

  6. 易宝典——玩转O365中的EXO服务 之三十六 为软删除邮箱启用就地保留

    在企业应用中有时候会碰到如下场景.有员工已经离职了,其用户账户和邮箱已经在系统中被进行了删除操作.但是,在该员工在职期间,并未发现需要对其邮箱启用保留的必要.而现在却因为某种原因需要对其邮箱内容进行保 ...

  7. linux脚本查看变量类型,Shell变量:Shell变量的定义、删除变量、只读变量、变量类型...

    变量是任何一种编程语言都必不可少的组成部分,变量用来存放各种数据.脚本语言在定义变量时通常不需要指明类型,直接赋值就可以,Shell 变量也遵循这个规则. 在 Bash shell 中,每一个变量的值 ...

  8. Shell变量:Shell变量的定义、删除变量、只读变量、变量类型

    Shell支持自定义变量. 定义变量 定义变量时,变量名不加美元符号($),如: variableName="value" 注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编 ...

  9. shell--2--shell变量 定义变量 使用变量 只读变量 删除变量 变量类型 Shell字符串 shell函数

    Shell变量 @1变量即在程序运行过程中它的值是允许改变的量 @2变量是用一串固定的字符来表示不固定的值的一种方法 @3变量是一种使用方便的占位符,用于引用计算机内存地址,该地址可以存储Script ...

最新文章

  1. javascript模块化之CommonJS、AMD、CMD、UMD、ES6
  2. 关于不同的MySQL复制解决方案概述
  3. Linux循环链表删除节点,删除循环单链表开头元素
  4. ML重要概念:梯度(Gradient)与梯度下降法(Gradient Descent)
  5. python大数字计算时没有响应_linux-为什么在尝试计算非常大的数字时Python会“抢先”挂起?...
  6. Java学习笔记二十二:Java的方法重写
  7. 对linux的mv命令设计测试用例,测试用例中的细节 - 八音弦的个人空间 - OSCHINA - 中文开源技术交流社区...
  8. transition
  9. java 继承示例_Java中的继承类型以及示例
  10. 多维数据库介绍【转】
  11. LINQ学习中需要明确的几点问题
  12. Java程序猿面试体会,还没找到工作的“猿猿们”看过来!
  13. 米思齐Mixly图形化编程---数管码时钟
  14. 企业微信文件会过期吗?
  15. Flask的jinjia2语句最详细容易理解教程
  16. 大数据的75个名词解释
  17. 产品设计:产品设计中模块化设计的再认识与思考
  18. Libpcap Libnet 各个接口pai 巨细
  19. Node.js Async Await in ES7
  20. 书呆子rico_书呆子父母指南:何时以及如何向您的孩子介绍《星球大战》

热门文章

  1. Python爬虫之每天给她发一个笑话
  2. 【iOS】SDWebImage
  3. C++ 新手必备:文件分割器
  4. how to do English Scientific Presentation
  5. 解决软件开发中的多个痛点——华为软件开发云
  6. 【呕心沥血】整理全栈自动化测试技术(三):如何编写技术方案
  7. 拉姆达表达式 filter与map的讲解
  8. 手机赚钱软件整套出售,类似赚钱宝、米赚、学生赚
  9. python擅长做什么工作_【一点资讯】Python是个什么鬼?为何火遍国内外……
  10. 计算机学院毕业设计文化衫,中国农业大学理学院 通知公告 【毕业季】理学院2019年毕业文化衫设计方案征集(有更新)...