微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

gorm 简单调用源码分析

gorm 介绍:

基于golang的orm框架,相关文档:http://doc.gorm.io/

demo:

package main

import (
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
)

type Product struct {
  gorm.Model
  Code string
  Price uint
}

func main() {
  db,err := gorm.Open("sqlite3","test.db")
  if err != nil {
    panic("Failed to connect database")
  }
  defer db.Close()

  // Migrate the schema
  db.AutoMigrate(&Product{})

  // Create
  db.Create(&Product{Code: "L1212",Price: 1000})

  // Read
  var product Product
  db.First(&product, 1) // find product with id 1
  db.First(&product,"code = ?","L1212") // find product with code l1212

  // Update - update product's price to 2000
  db.Model(&product).Update("Price", 2000)

  // Delete - delete product
  db.Delete(&product)
}

源码分析:

现在我们从main函数开始分析:
open实现了sql driver的注册,并且将原生sql操作封装为db对象

核心结构体:

db相关信息,每次绑定不同的value,操作对象例如product{}
type DB struct {
    Value        interface{}
    Error        error
    RowsAffected int64

    // single db
    db                sqlCommon //原生db.sql对象,包含query相关的原生方法
    blockGlobalUpdate bool
    logMode           int
    logger            logger
    search            *search //保存搜索的条件where,limit,group,比如调用db.clone()时,会指定search
    values            map[string]interface{}

    // global db
    parent        *DB
    callbacks     *Callback //当前sql绑定的函数调用
    dialect       Dialect //不同数据库适配注册sql.db
    singularTable bool
}

// 保存当前sql执行相关信息
type Scope struct {
    Search          *search // 检索条件
    Value           interface{}
    sql             string //sql
    sqlVars         []interface{}
    db              *DB //sql.db
    instanceID      string
    primaryKeyField *Field
    skipLeft        bool
    fields          *[]*Field //字段
    selectAttrs     *[]string
}

// 保存各种操作需要执行的调用链,例如create函数,需要调用creates数组中所有的函数
type Callback struct {
    creates    []*func(scope *Scope)
    updates    []*func(scope *Scope)
    deletes    []*func(scope *Scope)
    queries    []*func(scope *Scope)
    rowQueries []*func(scope *Scope)
    processors []*CallbackProcessor
}

注册db:

func Open(dialect string,args ...interface{}) (db *DB,err error) {

    // 检查传参
    var source string
    var dbsql sqlCommon

    switch value := args[0].(type) {
    case string:
        var driver = dialect
        if len(args) == 1 {
            source = value
        } else if len(args) >= 2 {
            driver = value
            source = args[1].(string)
        }
        dbsql,err = sql.Open(driver,source)
    case sqlCommon:
        dbsql = value
    }

    db = &DB{
        db:        dbsql,logger:    defaultLogger,//logger
        values:    map[string]interface{}{},callbacks: DefaultCallback,dialect:   newDialect(dialect,dbsql),}
    db.parent = db
    // 检验是否能ping
}

接下来看下create函数,demo中的create函数往product中增加记录

func (s *DB) Create(value interface{}) *DB {
    scope := s.NewScope(value) // value是product对象 
    //回调creates数组中的所有函数,然后将db返回
    return scope.callCallbacks(s.parent.callbacks.creates).db
}

// NewScope create a scope for current operation
func (s *DB) NewScope(value interface{}) *Scope {
    dbClone := s.clone() // 从当前db对象s拷贝出新db
    dbClone.Value = value // 绑定value为product对象
    return &Scope{db: dbClone,Search: dbClone.search.clone(),Value: value} //返回新的scope对象
}

那creates数组中的函数应该有哪些,回看open函数中并没有直接定义,那么还有一种初始化方法,就是包内init方法,我们可以看下gorm包中底下的所有的.go文件。果然在callback_create.go文件中,定义了init方法,里面对一些回调函数进行append:

package gorm

// Define callbacks for creating
func init() {
    DefaultCallback.Create().Register("gorm:begin_transaction",beginTransactionCallback)
    DefaultCallback.Create().Register("gorm:before_create",beforeCreateCallback)
    DefaultCallback.Create().Register("gorm:save_before_associations",saveBeforeAssociationsCallback)
    DefaultCallback.Create().Register("gorm:update_time_stamp",updateTimeStampForCreateCallback)
    DefaultCallback.Create().Register("gorm:create",createCallback)
    DefaultCallback.Create().Register("gorm:force_reload_after_create",forceReloadAfterCreateCallback)
    DefaultCallback.Create().Register("gorm:save_after_associations",saveAfterassociationsCallback)
    DefaultCallback.Create().Register("gorm:after_create",afterCreateCallback)
    DefaultCallback.Create().Register("gorm:commit_or_rollback_transaction",commitOrRollbackTransactionCallback)
}

我们看看Callback.Create().Register()里面的逻辑:

type CallbackProcessor struct {
    name      string              // current callback's name
    before    string              // register current callback before a callback
    after     string              // register current callback after a callback
    replace   bool                // replace callbacks with same name
    remove    bool                // delete callbacks with same name
    kind      string              // callback type: create,update,delete,query,row_query
    processor *func(scope *Scope) // callback handler
    parent    *Callback
}

// 定义了kind为create
func (c *Callback) Create() *CallbackProcessor {
    return &CallbackProcessor{kind: "create",parent: c}
}

// Register a new callback,refer `Callbacks.Create`
func (cp *CallbackProcessor) Register(callbackName string,callback func(scope *Scope)) {
    // 如果为查询类型
    if cp.kind == "row_query" {
        if cp.before == "" && cp.after == "" && callbackName != "gorm:row_query" {
            log.Printf("Registing RowQuery callback %v without specify order with Before(),After(),applying Before('gorm:row_query') by default for compatibility...\n",callbackName)
            cp.before = "gorm:row_query"
        }
    }

    // 设置callbackName,callback函数
    cp.name = callbackName
    cp.processor = &callback
    cp.parent.processors = append(cp.parent.processors,cp)

    // append 到create数组
    cp.parent.reorder()
}

// reorder all registered processors,and reset CRUD callbacks
func (c *Callback) reorder() {
    var creates,updates,deletes,queries,rowQueries []*CallbackProcessor

    for _,processor := range c.processors {
        if processor.name != "" {
            switch processor.kind {
            case "create":
                // append 至creates数组
                creates = append(creates,processor)
            case "update":
                updates = append(updates,processor)
            case "delete":
                deletes = append(deletes,processor)
            case "query":
                queries = append(queries,processor)
            case "row_query":
                rowQueries = append(rowQueries,processor)
            }
        }
    }

    c.creates = sortProcessors(creates)
    c.updates = sortProcessors(updates)
    c.deletes = sortProcessors(deletes)
    c.queries = sortProcessors(queries)
    c.rowQueries = sortProcessors(rowQueries)
}

creates初始化已经大体了解了,那看回数组本身,它拥有以下方法

DefaultCallback.Create().Register("gorm:begin_transaction",commitOrRollbackTransactionCallback)

开始事务,create前检查,更新时间戳,create后检查,事务提交或回滚等等。
事务代码

func beginTransactionCallback(scope *Scope) {
    scope.Begin()
}

func commitOrRollbackTransactionCallback(scope *Scope) {
    scope.CommitOrRollback()
}

// Begin start a transaction
func (scope *Scope) Begin() *Scope {
    // 使用scope中的sqldb进行操作
    if db,ok := scope.sqlDB().(sqlDb); ok {
        if tx,err := db.Begin(); err == nil {
            scope.db.db = interface{}(tx).(sqlCommon)
            scope.InstanceSet("gorm:started_transaction",true)
        }
    }
    return scope
}

// CommitOrRollback commit current transaction if no error happened,otherwise will rollback it
func (scope *Scope) CommitOrRollback() *Scope {
    if _,ok := scope.InstanceGet("gorm:started_transaction"); ok {
        if db,ok := scope.db.db.(sqlTx); ok {
            if scope.HasError() {
                db.Rollback()
            } else {
                scope.Err(db.Commit())
            }
            scope.db.db = scope.db.parent.db
        }
    }
    return scope
}

创建代码

func createCallback(scope *Scope) {
    if !scope.HasError() {
        defer scope.trace(NowFunc())

        var (
            columns,placeholders        []string
            blankColumnsWithDefaultValue []string
        )

        for _,field := range scope.Fields() {
            if scope.changeableField(field) {
                if field.Isnormal {
                    if field.IsBlank && field.HasDefaultValue {
                        blankColumnsWithDefaultValue = append(blankColumnsWithDefaultValue,scope.Quote(field.dbname))
                        scope.InstanceSet("gorm:blank_columns_with_default_value",blankColumnsWithDefaultValue)
                    } else if !field.IsPrimaryKey || !field.IsBlank {
                        columns = append(columns,scope.Quote(field.dbname))
                        placeholders = append(placeholders,scope.AddToVars(field.Field.Interface()))
                    }
                } else if field.Relationship != nil && field.Relationship.Kind == "belongs_to" {
                    for _,foreignKey := range field.Relationship.Foreigndbnames {
                        if foreignField,ok := scope.FieldByName(foreignKey); ok && !scope.changeableField(foreignField) {
                            columns = append(columns,scope.Quote(foreignField.dbname))
                            placeholders = append(placeholders,scope.AddToVars(foreignField.Field.Interface()))
                        }
                    }
                }
            }
        }

        var (
            returningColumn = "*"
            quotedTableName = scope.QuotedTableName()
            primaryField    = scope.PrimaryField()
            extraOption     string
        )

        if str,ok := scope.Get("gorm:insert_option"); ok {
            extraOption = fmt.Sprint(str)
        }

        if primaryField != nil {
            returningColumn = scope.Quote(primaryField.dbname)
        }

        lastInsertIDReturningSuffix := scope.Dialect().LastInsertIDReturningSuffix(quotedTableName,returningColumn)

        // 拼接执行sql
        if len(columns) == 0 {
            scope.Raw(fmt.Sprintf(
                "INSERT INTO %v %v%v%v",quotedTableName,scope.Dialect().DefaultValueStr(),addExtraSpaceIfExist(extraOption),addExtraSpaceIfExist(lastInsertIDReturningSuffix),))
        } else {
            scope.Raw(fmt.Sprintf(
                "INSERT INTO %v (%v) VALUES (%v)%v%v",scope.QuotedTableName(),strings.Join(columns,","),strings.Join(placeholders,))
        }

        // execute create sql
        if lastInsertIDReturningSuffix == "" || primaryField == nil {
            if result,err := scope.sqlDB().Exec(scope.sql,scope.sqlVars...); scope.Err(err) == nil {
                // set rows affected count
                scope.db.RowsAffected,_ = result.RowsAffected()

                // set primary value to primary field
                if primaryField != nil && primaryField.IsBlank {
                    if primaryValue,err := result.LastInsertId(); scope.Err(err) == nil {
                        scope.Err(primaryField.Set(primaryValue))
                    }
                }
            }
        } else {
            if primaryField.Field.CanAddr() {
                if err := scope.sqlDB().QueryRow(scope.sql,scope.sqlVars...).Scan(primaryField.Field.Addr().Interface()); scope.Err(err) == nil {
                    primaryField.IsBlank = false
                    scope.db.RowsAffected = 1
                }
            } else {
                scope.Err(ErrUnaddressable)
            }
        }
    }
}

简单crub操作的调用流程分析如上。

原文地址:https://www.jb51.cc/go/187343.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐