G

[Golang] GORM框架 逐步分析→FirstOrCreate

RoLingG 2024-10-10

分析FirstOrCreate

首先我们来看看它的源码:

// 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.
//
// Using FirstOrCreate in conjunction with Assign will result in an update to the database even if the record exists.
//
//    // assign an email if the record is not found
//    result := db.Where(User{Name: "non_existing"}).Attrs(User{Email: "fake@fake.org"}).FirstOrCreate(&user)
//    // user -> User{Name: "non_existing", Email: "fake@fake.org"}
//    // result.RowsAffected -> 1
//
//    // assign email regardless of if record is found
//    result := db.Where(User{Name: "jinzhu"}).Assign(User{Email: "fake@fake.org"}).FirstOrCreate(&user)
//    // user -> User{Name: "jinzhu", Age: 20, Email: "fake@fake.org"}
//    // result.RowsAffected -> 1
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},
    })

    result := queryTx.Find(dest, conds...)
    if result.Error != nil {
        tx.Error = result.Error
        return tx
    }

    if result.RowsAffected == 0 {
        if c, ok := result.Statement.Clauses["WHERE"]; ok {
            if where, ok := c.Expression.(clause.Where); ok {
                result.assignInterfacesToValue(where.Exprs)
            }
        }

        // initialize with attrs, conds
        if len(db.Statement.attrs) > 0 {
            result.assignInterfacesToValue(db.Statement.attrs...)
        }

        // initialize with attrs, conds
        if len(db.Statement.assigns) > 0 {
            result.assignInterfacesToValue(db.Statement.assigns...)
        }

        return tx.Create(dest)
    } else if len(db.Statement.assigns) > 0 {
        exprs := tx.Statement.BuildCondition(db.Statement.assigns[0], db.Statement.assigns[1:]...)
        assigns := map[string]interface{}{}
        for _, expr := range exprs {
            if eq, ok := expr.(clause.Eq); ok {
                switch column := eq.Column.(type) {
                case string:
                    assigns[column] = eq.Value
                case clause.Column:
                    assigns[column.Name] = eq.Value
                }
            }
        }

        return tx.Model(dest).Updates(assigns)
    }
    
    return tx
}

逐步分析它的源码:

  1. 获取实例

    tx = db.getInstance()

    获取当前数据库操作的实例。

  2. 配置查询并执行查询

    queryTx := db.Session(&Session{}).Limit(1).Order(clause.OrderByColumn{
        Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
    }))
    result := queryTx.Find(dest, conds...)

    创建一个新的会话,并限制查询结果为一条记录,按照主键排序。之后使用 Find 方法查找符合条件的记录,并将结果保存到 dest 指向的变量中。

    dest 是什么具体去看这一篇《GORM的DB核心》
  3. 处理查询错误

    if result.Error != nil {
        tx.Error = result.Error
        return tx
    }

    如果查询过程中出现错误,将错误保存到事务对象中并返回。

  4. 检查是否找到记录

    if result.RowsAffected == 0 {

    如果未找到记录(即 RowsAffected 为 0),则执行以下操作:

  5. 构建条件表达式

    if c, ok := result.Statement.Clauses["WHERE"]; ok {
        if where, ok := c.Expression.(clause.Where); ok {
            result.assignInterfacesToValue(where.Exprs)
        }
    }

    从查询中提取 WHERE 条件,并将其分配给结果变量。

  6. 使用 attrs 初始化

    if len(db.Statement.attrs) > 0 {
        result.assignInterfacesToValue(db.Statement.attrs...)
    }

    如果存在 attrs(通常用于 FirstOrInit),则使用它们初始化 dest

  7. 使用 assigns 初始化

    if len(db.Statement.assigns) > 0 {
        result.assignInterfacesToValue(db.Statement.assigns...)
    }

    如果存在 assigns,则使用它们初始化 dest

  8. 创建新记录

    return tx.Create(dest)

    如果未找到记录,则使用 Create 方法创建新记录。

  9. 更新现有记录

    else if len(db.Statement.assigns) > 0 {
        exprs := tx.Statement.BuildCondition(db.Statement.assigns[0], db.Statement.assigns[1:]...)
        assigns := map[string]interface{}{}
        for _, expr := range exprs {
            if eq, ok := expr.(clause.Eq); ok {
                switch column := eq.Column.(type) {
                case string:
                    assigns[column] = eq.Value
                case clause.Column:
                    assigns[column.Name] = eq.Value
                }
            }
        }
    
        return tx.Model(dest).Updates(assigns)
    }

    如果找到了记录,但存在 assigns,则构建更新表达式,并将 assigns 中的值更新到数据库中的记录。

  10. 返回事务对象

    return tx

    返回事务对象,包含操作结果。

PREV
[Golang] GORM框架 锁
NEXT
[Golang] Channel原理

评论(0)

发布评论