G

[Golang] GORM框架 核心

RoLingG 2024-10-10

GORM的核心:DB

结构体DB是 GORM 框架中的核心部分,我们来了解一下这个结构体的内容:

// DB GORM DB definition
type DB struct {
    *Config
    Error        error
    RowsAffected int64
    Statement    *Statement
    clone        int
}
  1. *Config:这是一个指向 Config 结构体的指针,Config 结构体包含了 GORM 配置信息,比如数据库连接的详细信息(如数据库类型、DSN、最大打开连接数等)。

    // Config GORM config
    type Config struct {
        // GORM perform single create, update, delete operations in transactions by default to ensure database data integrity
        // You can disable it by setting `SkipDefaultTransaction` to true
        SkipDefaultTransaction bool
        // NamingStrategy tables, columns naming strategy
        NamingStrategy schema.Namer
        // FullSaveAssociations full save associations
        FullSaveAssociations bool
        // Logger
        Logger logger.Interface
        // NowFunc the function to be used when creating a new timestamp
        NowFunc func() time.Time
        // DryRun generate sql without execute
        DryRun bool
        // PrepareStmt executes the given query in cached statement
        PrepareStmt bool
        // DisableAutomaticPing
        DisableAutomaticPing bool
        // DisableForeignKeyConstraintWhenMigrating
        DisableForeignKeyConstraintWhenMigrating bool
        // IgnoreRelationshipsWhenMigrating
        IgnoreRelationshipsWhenMigrating bool
        // DisableNestedTransaction disable nested transaction
        DisableNestedTransaction bool
        // AllowGlobalUpdate allow global update
        AllowGlobalUpdate bool
        // QueryFields executes the SQL query with all fields of the table
        QueryFields bool
        // CreateBatchSize default create batch size
        CreateBatchSize int
        // TranslateError enabling error translation
        TranslateError bool
    
        // ClauseBuilders clause builder
        ClauseBuilders map[string]clause.ClauseBuilder
        // ConnPool db conn pool
        ConnPool ConnPool
        // Dialector database dialector
        Dialector
        // Plugins registered plugins
        Plugins map[string]Plugin
    
        callbacks  *callbacks
        cacheStore *sync.Map
    }
    
    /*
    SkipDefaultTransaction:布尔值,用于指定是否跳过默认的事务处理。默认情况下,GORM 会在执行创建、更新、删除操作时使用事务来确保数据一致性。
    
    NamingStrategy:用于定义表和列的命名策略的接口 schema.Namer。
    
    FullSaveAssociations:布尔值,用于指定是否在保存模型时保存所有关联的模型。
    
    Logger:用于记录日志的接口 logger.Interface。
    
    NowFunc:一个返回当前时间的函数,用于生成时间戳。
    
    DryRun:布尔值,用于指定是否在生成 SQL 语句后不执行它。
    
    PrepareStmt:布尔值,用于指定是否对查询进行预处理。
    
    DisableAutomaticPing:布尔值,用于指定是否禁用自动 Ping 操作,这可以用于减少数据库连接的心跳检测。
    
    DisableForeignKeyConstraintWhenMigrating:布尔值,用于指定在迁移时是否禁用外键约束。
    
    IgnoreRelationshipsWhenMigrating:布尔值,用于指定在迁移时是否忽略关系。
    
    DisableNestedTransaction:布尔值,用于指定是否禁用嵌套事务。
    
    AllowGlobalUpdate:布尔值,用于指定是否允许全局更新。
    
    QueryFields:布尔值,用于指定是否在执行 SQL 查询时使用表的所有字段。
    
    CreateBatchSize:整型值,用于定义批量创建记录时的默认批次大小。
    
    TranslateError:布尔值,用于指定是否启用错误翻译。
    
    ClauseBuilders:一个映射,用于定义子句构建器 clause.ClauseBuilder。
    
    ConnPool:数据库连接池。
    
    Dialector:数据库方言器,用于处理不同数据库的方言差异。
    
    Plugins:注册的插件映射。
    
    callbacks:一个私有字段,用于存储 GORM 回调函数。
    
    cacheStore:一个私有字段,用于存储缓存。
    */
    // callbacks gorm callbacks manager
    type callbacks struct {
        processors map[string]*processor
    }
    
    type processor struct {
        db        *DB
        Clauses   []string
        fns       []func(*DB)
        callbacks []*callback
    }
    
    type callback struct {
        name      string
        before    string
        after     string
        remove    bool
        replace   bool
        match     func(*DB) bool
        handler   func(*DB)
        processor *processor
    }
    
    /*
    
    callbacks 结构体是用来管理回调函数的。GORM 允许在模型的生命周期内的不同阶段执行回调函数,例如在创建、查询、更新或删除记录时。以下是这些结构体和字段的作用:
    
    callbacks 结构体:
    processors:一个映射,键是回调的类型(如 create、query、update、delete 等),值是指向 processor 结构体的指针。
    
    processor 结构体:
    db:指向 DB 结构体的指针,包含了数据库操作的上下文信息。
    Clauses:字符串切片,用于存储构建 SQL 语句时使用的子句名称。
    fns:函数切片,每个函数接收 *DB 并返回 *DB,用于修改 DB 的状态。
    callbacks:callback 结构体的切片,存储具体的回调函数。
    
    callback 结构体:
    name:回调的名称。
    before:指定回调在哪个操作之前执行。
    after:指定回调在哪个操作之后执行。
    remove:布尔值,用于指定是否移除回调。
    replace:布尔值,用于指定是否替换现有的回调。
    match:一个函数,接收 *DB 作为参数并返回布尔值,用于判断当前的 DB 上下文是否匹配回调。
    handler:回调函数,接收 *DB 作为参数。
    processor:指向 processor 结构体的指针,包含了回调所属的处理器。
    
    */
  2. Error:用于存储数据库操作过程中遇到的错误。
  3. RowsAffected:表示上一次数据库操作影响的行数。这个字段在执行插入、更新或删除操作后特别有用,因为它可以让你知道有多少行数据被修改。
  4. Statement:这是一个指向 Statement 结构体的指针,Statement 结构体包含了 SQL 语句的相关信息,比如 SQL 语句本身、参数绑定等。
  5. // Statement statement
    type Statement struct {
        *DB
        TableExpr            *clause.Expr
        Table                string
        Model                interface{}
        Unscoped             bool
        Dest                 interface{}
        ReflectValue         reflect.Value
        Clauses              map[string]clause.Clause
        BuildClauses         []string
        Distinct             bool
        Selects              []string // selected columns
        Omits                []string // omit columns
        Joins                []join
        Preloads             map[string][]interface{}
        Settings             sync.Map
        ConnPool             ConnPool
        Schema               *schema.Schema
        Context              context.Context
        RaiseErrorOnNotFound bool
        SkipHooks            bool
        SQL                  strings.Builder
        Vars                 []interface{}
        CurDestIndex         int
        attrs                []interface{}
        assigns              []interface{}
        scopes               []func(*DB) *DB
    }
    
    /*
    *DB:这是一个指向 DB 结构体的指针,包含了数据库操作的上下文信息。
    
    TableExpr:指向 clause.Expr 类型的指针,用于表示 SQL 查询中的表表达式。
    
    Table:表示当前操作的表名。
    
    Model:用于指定当前操作的模型(Model),通常是结构体类型的实例。
    
    Unscoped:布尔值,用于控制是否忽略软删除的记录。
    
    Dest:用于指定查询结果要映射到的目标变量。
    
    ReflectValue:反射值,用于操作 Dest 中的值。
    
    Clauses:一个映射,用于存储构建 SQL 语句时使用的子句(如 WHERE、ORDER BY 等)。
    
    BuildClauses:一个字符串切片,用于存储构建 SQL 语句时需要的子句名称。
    
    Distinct:布尔值,用于指定查询是否需要去重。
    
    Selects:字符串切片,用于指定查询时需要选择的列。
    
    Omits:字符串切片,用于指定查询时需要省略的列。
    
    Joins:join 类型的切片,用于存储连接(JOIN)操作的信息。
    
    Preloads:映射,用于预加载关联的模型。
    
    Settings:sync.Map 类型,用于存储构建 SQL 语句时的设置。
    
    ConnPool:数据库连接池。
    
    Schema:指向 schema.Schema 的指针,包含了数据库表的模式信息。
    
    Context:context.Context 类型的值,用于控制数据库操作的上下文,比如超时、取消等。
    
    RaiseErrorOnNotFound:布尔值,用于指定当查询不到记录时是否抛出错误。
    
    SkipHooks:布尔值,用于指定是否跳过钩子(Hooks)的执行。
    
    SQL:strings.Builder 类型,用于构建最终的 SQL 语句。
    
    Vars:接口切片,用于存储 SQL 语句中的变量值。
    
    CurDestIndex:当前目标变量的索引。
    
    attrs:接口切片,用于存储查询时的额外属性。
    
    assigns:接口切片,用于存储需要赋值的字段。
    
    scopes:函数切片,每个函数接收 *DB 并返回一个新的 *DB,用于修改查询的上下文。
    */
  6. clone:这个字段的具体作用在 GORM 的官方文档中没有明确说明,但根据命名和上下文推测,它可能用于控制 DB 结构体的克隆行为,以确保并发操作时的线程安全。

    实际使用示例

    我们来看看GORM的Save()函数:

    // Save updates value in database. If value doesn't contain a matching primary key, value is inserted.
    func (db *DB) Save(value interface{}) (tx *DB) {
     //获取当前 DB 实例的一个副本。
     tx = db.getInstance()
     //设置 Statement 结构体中的 Dest 字段为传入的 value,这通常是要保存的模型。
     tx.Statement.Dest = value    
    
     //使用反射获取 value 的实际值
     reflectValue := reflect.Indirect(reflect.ValueOf(value))
     //循环解引用,如果反射传回来的值是指针或者是接口就会触发解引用
     for reflectValue.Kind() == reflect.Ptr || reflectValue.Kind() == reflect.Interface {
        reflectValue = reflect.Indirect(reflectValue)
     }
    
     switch reflectValue.Kind() {
     case reflect.Slice, reflect.Array:
         //切片和队列如果要进行批量插入或者更新,没有ON CONFLICT字段,则自动加上ON CONFLICT,即冲突时更新所有字段。然后执行创建回调。
        if _, ok := tx.Statement.Clauses["ON CONFLICT"]; !ok {
           tx = tx.Clauses(clause.OnConflict{UpdateAll: true})
        }
        tx = tx.callbacks.Create().Execute(tx.Set("gorm:update_track_time", true))
     case reflect.Struct:
         //如果模型的主键字段值都是零值,假定是插入操作,执行创建回调。
        if err := tx.Statement.Parse(value); err == nil && tx.Statement.Schema != nil {
           for _, pf := range tx.Statement.Schema.PrimaryFields {
              if _, isZero := pf.ValueOf(tx.Statement.Context, reflectValue); isZero {
                 return tx.callbacks.Create().Execute(tx)
              }
           }
        }
    
        fallthrough
     default:
         //要更新的字段切片非0来判断是否已经选择了特定的字段进行更新
        selectedUpdate := len(tx.Statement.Selects) != 0
        // when updating, use all fields including those zero-value fields
        if !selectedUpdate {
            //没有需要指定更新的字段,则默认更新所有字段
           tx.Statement.Selects = append(tx.Statement.Selects, "*")
        }
    
        updateTx := tx.callbacks.Update().Execute(tx.Session(&Session{Initialized: true}))
    
         //如果更新操作没有错误,没有影响任何行,不是 DryRun 模式,并且没有指定 Selects,则假定是插入操作。
        if updateTx.Error == nil && updateTx.RowsAffected == 0 && !updateTx.DryRun && !selectedUpdate {
            //如果是插入操作,则添加ON CONFLICT子句,设置为全部更新,然后执行创建操作。
           return tx.Clauses(clause.OnConflict{UpdateAll: true}).Create(value)
        }
        return updateTx
     }
     return

这里需要注意的地方在于`return tx.Clauses(clause.OnConflict{UpdateAll: true}).Create(value)`,并不是所有数据库都能适配 `on conflict` 子句的,如果不适配,则会造成SQL语法错误。
PREV
[Kafka] 高可用实例
NEXT
[Golang] GORM框架 Exec和Raw

评论(0)

发布评论