以下介绍基于 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)
分析解析路径
- 当调用
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
有疑问加站长微信联系(非本文作者)
