Gorm 的黑魔法

开发过程中,看到同事的代码写了这么一段:

db = db.Session(&gorm.Session{Context: db.Statement.Context}).FirstOrCreate(&entity)
if db.Error !=nil{
    return components.ErrorDbInsert.WrapPrintf(db.Error, "Insert error, entity:%s", utils.ToJson(entity))
}
if db.RowsAffected == 0 {
    return components.ErrorAlreadyExist
}

我不禁感到疑惑, gormRowsAffected 在进行查询,如果查到数据,也是有值的,为什么在这里可以用 RowsAffected == 0 来判断数据已存在?

抱着这个疑问,我点开了 FirstOrCreate 的代码:

func (db *DB) FirstOrCreate(dest interface{}, conds ...interface{}) (tx *DB) {
    queryTx := db.Limit(1).Order(clause.OrderByColumn{
        Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
    })

    if tx = queryTx.Find(dest, conds...); queryTx.RowsAffected == 0 {
        ...
        return tx.Create(dest)
    } else if len(db.Statement.assigns) > 0 {
        ...
        return tx.Model(dest).Updates(assigns)
    }

    return db
}

我们可以很容易地发现,在 Find 查到数据且 assigns 没有值的情况下, return 的是 db,而其他情况下 return 的是 tx。直觉告诉我,原因大概率在这个上面。

Limit、Order、Find等许多函数都调用了同一个函数 db.getInstance()

func (db *DB) getInstance() *DB {
    if db.clone > 0 {
        tx := &DB{Config: db.Config, Error: db.Error}

        if db.clone == 1 { // 吐槽一下这里的魔法值,理解起来真不容易
            // clone with new statement
            tx.Statement = &Statement{
                DB:       tx,
                ConnPool: db.Statement.ConnPool,
                Context:  db.Statement.Context,
                Clauses:  map[string]clause.Clause{},
                Vars:     make([]interface{}, 0, 8),
            }
        } else {
            // with clone statement
            tx.Statement = db.Statement.clone()
            tx.Statement.DB = tx
        }

        return tx
    }

    return db
}

这个函数很简单,

db.getInstance()的小结:

看到这里,这个”黑魔法”的原理已经呈现在我们眼前:

在调用 FirstOrCreate 函数的时候,如果此时 clone 不为0,则会在调用 Limit 函数的时候生成一个新的实例 tx

因此在查到数据且不进行 Update 的情况下,函数会直接返回 db,而 RowsAffected 为 0。

基于上述理论,大胆猜想 Session 这个函数必定会改变 dbclone 属性,查看 Session 源码后,我如愿找到了:

//Session create new db session
func(db *DB) Session(config *Session) *DB {
    var(
      txConfig = *db.Config
      tx       = &DB{
         Config:    &txConfig,
         Statement: db.Statement,
         Error:     db.Error,
         clone:     1, // 设置 clone 的默认值为1
      }
  )

...
    // 回顾 getInstance 函数对 clone = 2 时的处理,NewDB 的含义不言而喻(再次吐槽魔法值)
    if !config.NewDB {
      tx.clone = 2
  }

...

    return tx
}

发现在新版本的 gorm ,金柱大佬直接把”魔仙棒”给没收了,不管结果如何,都返回 tx,因此新版本( v1.23.0之后)的 gorm 将无法使用这个”黑魔法”:

// FirstOrCreate finds the first matching record, otherwise if not found creates a new instance with given conds.

// Each conds must be a struct or map.

func (db *DB) FirstOrCreate(dest interface{}, conds ...interface{}) (tx *DB) {
    tx = db.getInstance()
    queryTx := db.Session(&Session{}).Limit(1).Order(clause.OrderByColumn{
        Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
    })
    if result := queryTx.Find(dest, conds...); result.Error == nil {
        if result.RowsAffected == 0 {
            ...
            return tx.Create(dest)
        } else if len(db.Statement.assigns) > 0 {
            ...
            return tx.Model(dest).Updates(assigns)
        }
    } else {
        tx.Error = result.Error
    }
    return tx
}

事实上,这个”黑魔法”的使用是不符合 RowsAffected 的原本定义的,开发者把它当成一个bug优化掉也是理所当然,实际上的开发应当尽量不用。

“黑魔法”应当少用,但是值得探究,这可比生啃源码有趣得多。

Original: https://www.cnblogs.com/weirwei/p/16734918.html
Author: weirwei
Title: Gorm 的黑魔法

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/591860/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Javaweb06-JDBC

    1、jdbc.properties配置文件 jdbc.properties driverClass=com.mysql.jdbc.Driver jdbcUrl=jdbc:mysql…

    数据库 2023年6月16日
    085
  • haproxy服务部署

    haproxy haproxy 一、haproyx是什么 二、负载均衡类型 三、部署haproxy 1.源码部署haproxy 2.Haproxy搭建http负载均衡 一、hapr…

    数据库 2023年6月14日
    0110
  • 设计模式之(10)——桥接模式

    Hello,大家好,我们的设计模式系列中断了几天,今天我们继续,那么我们下面继续来说一种结构型设计模式,那就是大名鼎鼎的”桥接模式”。 定义:桥接模式的官方…

    数据库 2023年6月14日
    0102
  • MySQL的文件系统(Linux环境)

    /var/lib/mysql/ 可通过以下sql命令查看: mysql> show variables like ‘datadir’; +—————+–…

    数据库 2023年5月24日
    074
  • MySQL优化之索引解析

    索引的本质 MySQL索引或者说其他关系型数据库的索引的本质就只有一句话, 以空间换时间。 索引的作用 索引关系型数据库为了 加速对表中行数据检索的( 磁盘存储的) 数据结构 索引…

    数据库 2023年5月24日
    093
  • jenkins 忘记密码

    仅适用centos7 一、 忘记密码 终端输入: vi /root/.jenkins/secrets/initialAdminPassword 复制文本内的密码,进行登录,此密码可…

    数据库 2023年6月14日
    076
  • 2021长安杯wp

    案件背景 2021年4月25日,上午8点左右,警方接到被害人金某报案,声称自己被敲诈数万元;经询问,昨日金某被嫌疑人诱导果聊,下载了某果聊软件,导致自己的通讯录和果聊视频被嫌疑人获…

    数据库 2023年6月11日
    076
  • 容器化 | 在 Rancher 中部署 MySQL 集群

    我们已经介绍了如何在 Kubernetes 和 KubeSphere 上部署 RadonDB MySQL 集群。本文将演示如何在 Rancher[1] 上部署 RadonDB My…

    数据库 2023年5月24日
    0119
  • Linux–>开关机+用户管理指令

    shutdown关机 语法: shutdown -h 关机时间 now 立刻1 1分种后 s…

    数据库 2023年6月14日
    086
  • mybatis-延迟加载

    本文主要介绍下mybatis的延迟加载,从原理上介绍下怎么使用、有什么好处能规避什么问题。延迟加载一般用于级联查询(级联查询可以将主表不能直接查询的数据使用自定义映射规则调用字表来…

    数据库 2023年6月16日
    072
  • 访问网络共享(net use):发生系统错误 67。找不到网络名。

    使用\ip访问对方共享目录或使用net use \ip 时: 发生系统错误 67。找不到网络名。 以下几项启用: 1,网卡勾选”Microsoft网络客户端&#8221…

    数据库 2023年6月14日
    0175
  • mysql练习题emp,dept

    DROP DATABASE IF EXISTS emp; CREATE DATABASE emp; USE emp;  CREATE TABLE dept( &a…

    数据库 2023年6月9日
    0182
  • 如何重置postgresql用户密码

    如何重置postgresql用户密码 场景: 打算新建一个postgresql的数据库 FooDB 并把所有者权限赋给用户 foo 正常操作应该是:先创建用户foo,再用foo身份…

    数据库 2023年6月9日
    0108
  • 启程——博客之路

    憋了这么久还是忍不住开始写自己的博客了。。。之前总是看别人的博客,伸手党一个,但是时间久了,总有一些自己想说的话,想想分享一些技术、经验,也能记录自己的学习历程,毕竟编程这条路还很…

    数据库 2023年6月9日
    077
  • windows下使用pm2守护进程对laravel队列进行管理

    我们都知道在使用laravel的消息队列时,都需要一个守护进程对其进行管理 否则在服务器重启或者其他异常都会导致队列中断从而功能失效 当然,大部分项目都是在linux下运行,则可以…

    数据库 2023年6月14日
    088
  • 多商户商城系统功能拆解29讲-平台端营销-会员签到

    多商户商城系统,也称为B2B2C(BBC)平台电商模式多商家商城系统。可以快速帮助企业搭建类似拼多多/京东/天猫/淘宝的综合商城。 多商户商城系统支持商家入驻加盟,同时满足平台自营…

    数据库 2023年6月14日
    0100
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球