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)

大家都在看

  • 23种设计模式之责任链模式

    文章目录 概览 责任链模式的优缺点 责任链模式的结构和实现 * 模式的结构 模式的实现 总结 ; 概览 责任链模式(Chain of Responsibility Pattern)…

    数据库 2023年6月6日
    092
  • 面试记录

    JVM线程属于用户态还是内核态 当进程运行在ring3级别时为用户态,ring0级别时为内核态 有些操作需要有内核权限才能进行,那么有三种由用户态切换到内核态的情况: 系统调用:操…

    数据库 2023年6月16日
    086
  • Tomcat8下的Redis会话共享

    前言: 最近在做网站的升级,从 Tomcat7升级到 Tomcat8版本,因为没接触过,就以为升级下Tomcat的版本就万事大吉,可是天不如人愿,很顺利的将应用升级到了Tomcat…

    数据库 2023年6月14日
    0100
  • SNMP基础简介

    近来,公司产品开发涉及到SNMP方面的知识, 在此作一些总结,或许对您现在或者将来有用。 在目前越来越复杂的网络环境中,整个环境有各种各样的网络设备,为了能更好的对这些设备进行管理…

    数据库 2023年6月11日
    077
  • [spring]spring注解开发

    8.使用注解开发 1.bean spring4以后,注解依赖于aop包,确保你的lib中有它 确保开启了使用注解 2.组件代替bean实现自动注入 在配置文件中自动扫描包下的所有类…

    数据库 2023年6月16日
    075
  • JUC学习笔记(六)

    JUC 中提供了三种常用的辅助类,通过这些辅助类可以很好的解决线程数量过多时 Lock 锁的频繁操作。这三种辅助类为: CountDownLatch: 减少计数 CyclicBar…

    数据库 2023年6月6日
    091
  • 容器化 | 一文搞定镜像构建方式选型

    作者:安树博 青云科技 PaaS 中间件开发工程师从事 PaaS 中间件服务(Redis/Memcached 等)开发工作,热衷对 NoSQL 数据库领域内技术的学习与研究 官方镜…

    数据库 2023年5月24日
    071
  • 集合自序整理集

    集合和数组一样都是对多个数据进行存储操作的容器 * – 集合长度可变,数组长度固定 – 集合可以存储不同数据类型元素,数组只能存储单一数据类型元素 &#82…

    数据库 2023年6月9日
    0107
  • 2022-8-15 数据库 mysql 第一天

    Mysql数据库 数据库 数据库[根据数据结构组织、存储和管理数据的仓库]。它是有组织的、可共享的、统一管理的大量数据的集合,这些数据长期存储在计算机中。 [En] Databas…

    数据库 2023年5月24日
    052
  • MySQL 日志管理

    日志文件记录 MySQL 数据库运行期间发生的变化,当数据库遭到意外的损害时,可以通过日志文件查询出错原因,并进件数据恢复 MySQL 日志文件可以分成以下几类: 二进制日志:记录…

    数据库 2023年5月24日
    054
  • mysql数据库创建数据库创建用户授权

    Liunx下登录数据库 mysql -u 用户名 -p 创建myblog用户,本地登录,口令是myblog create user ‘myblog’@&#8…

    数据库 2023年6月11日
    078
  • Javaer 面试必背系列!超高频八股之三色标记法

    可达性分析可以分成两个阶段 根节点枚举 从根节点开始遍历对象图 前文提到过,在可达性分析中,第一阶段 “根节点枚举” 是必须 STW 的,不然如果分析过程中…

    数据库 2023年6月6日
    098
  • 牛客SQL刷题第一趴——非技术入门基础篇

    id device_id gender age university province 1 2138 male 21 北京大学 Beijing 2 3214 male 复旦大学 S…

    数据库 2023年5月24日
    094
  • 详解Threejs中的光源对象

    光源的分类 AmbientLight(环境光), PointLight(点光源), SpotLight(聚光源) 和 DirectionalLight(平行光)是基础光源 Hemi…

    数据库 2023年6月11日
    091
  • 你知道5分钟法则和10字节法则么?

    如果一条数据每5分钟被访问一次,那么它应该常驻在内存中。类似的,如果想存储只有0和1两个值的标志位,相比于将8个标志位打包为1个字节,将1个标志位单独存储为1个字节是更节约的选择。…

    数据库 2023年6月14日
    098
  • 源码安装Nginx以及用systemctl管理

    一、源码安装Nginx: 下载 nginx软件包 进入nginx-1.20.1目录 安装依赖 /configure软件检查( ./configure–prefix=/u…

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