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)

大家都在看

  • JUC学习

    如何正确停止线程? 停止线程应该是一种通知协作的方式,比如interrupt,但是它仅仅是通知线程,线程拥有完全的自主权,根据自身业务来判断什么时候停止,因为如果选择立即停止就可能…

    数据库 2023年6月16日
    0101
  • day42-反射01

    Java反射01 1.反射(reflection)机制 1.1反射机制问题 一个需求引出反射 请看下面问题: 根据配置文件 re.properties 指定信息,创建Cat对象并调…

    数据库 2023年6月11日
    0128
  • 使用 GitHub 协同开发规范

    记一下项目开发的规范,统一开发规范可以有效提高共同开发效率和代码质量 本文档图片的 winsoullin 理解为团队正式发布的仓库,Nightnessss 理解为个人仓库 Fork…

    数据库 2023年6月9日
    0118
  • String vs StringBuffer vs StringBuilder

    String vs StringBuffer vs StringBuilder 本文翻译自:https://www.digitalocean.com/community/tutor…

    数据库 2023年6月11日
    096
  • Nginx实现服务器端集群搭建

    Nginx实现服务器端集群搭建 Nginx与Tomcat部署 前面课程已经将Nginx的大部分内容进行了讲解,我们都知道了Nginx在高并发场景和处理静态资源是非常高性能的,但是在…

    数据库 2023年6月6日
    081
  • 电脑卡.磁盘占用100% .解惑找不到Superfetch等服务问题

    公司电脑没有固态。磁盘io比较慢. 经常打满100% *1. 打开任务管理器发现是 一个叫system和DCFWinService的服务一直在占用磁盘读写 2. 解决方向. 禁用掉…

    数据库 2023年6月14日
    0688
  • Java面向对象(下)作业

    首先我把题目先列到这里,可以仔细看一下题。 (1)设计一个名为Geometric的几何图形的抽象类,该类包括: ①两个名为color、filled属性分别表示图形颜色和是否填充。 …

    数据库 2023年6月11日
    0142
  • 数据库性能优化八大方案,你知道几个

    前言 毫不夸张的说咱们后端工程师,无论在哪家公司,呆在哪个团队,做哪个系统,遇到的第一个让人头疼的问题绝对是数据库性能问题。 如果我们有一套成熟的方法论,能让大家快速、准确的去选择…

    数据库 2023年6月14日
    079
  • 15 构造器(constructor)是否可被重写(override)

    构造器不能被重写,但能被重载 posted @2020-12-18 18:02 卫盾 阅读(144 ) 评论() 编辑 Original: https://www.cnblogs….

    数据库 2023年6月6日
    0101
  • 如何把返回的datatable按某个字段 排序 升序或者降序

    如何把返回的datatable按某个字段 排序 升序或者降序 DataTable dtdata = GetXmlData(doc, “DetailList”…

    数据库 2023年6月9日
    069
  • java~ForkJoinPool分而致之处理大数据

    ForkJoinPool的思想,是将大的集合进行拆分,计算处理之后,再把结果合并,这体现了多核时代的并行计算能力。 集合拆分成元素 List<integer> maps…

    数据库 2023年6月6日
    060
  • 双色球系统开发

    Java对彩票双色球系统开发的简单实现 双色球系统 案例: 中奖条件及奖金表 代码及解释 main方法代码: public static void main(String[] ar…

    数据库 2023年6月16日
    0119
  • MySQL 回表

    MySQL 回表 五花马,千金裘,呼儿将出换美酒,与尔同销万古愁。 一、简述 回表,顾名思义就是回到表中,也就是先通过普通索引扫描出数据所在的行,再通过行主键ID 取出索引中未包含…

    数据库 2023年6月14日
    078
  • Navicat 连接服务器不成功(Access denied for user ‘root’@ ‘*.*.*.*’ (using password: YES))

    出现的原因一般是服务器的root用户没有开启访问权限,一般来说值允许本地的访问。 解决方法: 一:第一种方法 1、首先打开xshell连接服务器的终端 2、以root权限登录 my…

    数据库 2023年6月11日
    073
  • 21粤比武

    先进行密码绕过,在这个界面迅速按下方向键,然后按下e进入编辑模式 找到linux16这一行,将lang编码后面的全部删掉,加上 <span class=”ne-text”&g…

    数据库 2023年6月11日
    099
  • 「 MySQL高级篇 」MySQL索引原理,设计原则

    404. 抱歉,您访问的资源不存在。 可能是URL不正确,或者对应的内容已经被删除,或者处于隐私状态。 [En] It may be that the URL is incorre…

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