目录

GORM 底层实现

  1. 执行查询
    gorm.io/gorm/callbacks/query.go::Query(db *gorm.DB)
  2. 扫描结果集
    gorm.io/gorm/scan.go::Scan(rows *sql.Rows, db *DB, initialized bool)

扫描结果集的过程

  1. 取出结果集的所有字段
  2. 根据 Find() 时传入的引用判断存放的容器。比如用 map[string]interface{},或者结构体切片 []User,其他类型不一一列出。以下描述的是结构体切片的流程。
  3. 根据结果集字段的个数,生成与其长度相等的 interface{} 类型的 values 切片,以及长度相等的 Field 类型的 fields 切片。
    • values 切片将会被重复使用
  4. 找出结构体内与结果集列字段匹配的字段,并且在 fields 切片与列字段相同下标的槽里面放入结构体字段的信息。
  5. 生成结构体变量
  6. 在 values 切片的每个槽都设置与 fields 切片相同位置上的字段类型。
    • values 的每个 item 是 interface{},如果不在每次扫描之前都重新赋值,就会影响到已赋值的数据。
  7. 调用原生库扫描数据行到 value 切片
  8. 取出 fields 里有效的 field,在 values 里面取出相同位置的数据,并使用 field 将得到的数据赋值到结构体变量里面。
  9. 结构体变量加入 ReflectValue
  10. 回到 5 继续扫描下一行
  1. 执行驱动(driving)表的查询,将结果按照 foreignKey 的值分组
  2. 取所有 foreignKey ,到被驱动(driven)表查询
  3. 对于 driven 表的查询结果,逐个根据 referenceKey 找到 driving 表的结果分组,给分组里每个对象的相应 field 赋值

如果是结构体指针,那么当 foreignKey 存在相同的数据时,同一个结构体指针会赋值给不同的 driving 表结果。一旦其中一个修改,另一个也会被修改。

每次调用条件或者 Find 都会获取新的数据库实例,因此不会出现共用一个数据库实例的情况。