01GORM源碼解讀

簡介

GORM 源碼解讀, 基于 v1.9.11 版本.

起步

官方文檔上入門的例子如下:

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{})

  // 創(chuàng)建
  db.Create(&Product{Code: "L1212", Price: 1000})

  // 讀取
  var product Product
  db.First(&product, 1) // 查詢id為1的product
  db.First(&product, "code = ?", "L1212") // 查詢code為l1212的product

  // 更新 - 更新product的price為2000
  db.Model(&product).Update("Price", 2000)

  // 刪除 - 刪除product
  db.Delete(&product)
}

數(shù)據(jù)庫連接

gorm.Open 開始看起吧, 看數(shù)據(jù)庫是怎么連接的:

// Open initialize a new db connection, need to import driver first, e.g:
//
//     import _ "github.com/go-sql-driver/mysql"
//     func main() {
//       db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
//     }
// GORM has wrapped some drivers, for easier to remember driver's import path, so you could import the mysql driver with
//    import _ "github.com/jinzhu/gorm/dialects/mysql"
//    // import _ "github.com/jinzhu/gorm/dialects/postgres"
//    // import _ "github.com/jinzhu/gorm/dialects/sqlite"
//    // import _ "github.com/jinzhu/gorm/dialects/mssql"
func Open(dialect string, args ...interface{}) (db *DB, err error) {
    if len(args) == 0 {
        err = errors.New("invalid database source")
        return nil, err
    }
    var source string
    var dbSQL SQLCommon
    var ownDbSQL bool

    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)
        ownDbSQL = true
    case SQLCommon:
        dbSQL = value
        ownDbSQL = false
    default:
        return nil, fmt.Errorf("invalid database source: %v is not a valid type", value)
    }

    db = &DB{
        db:        dbSQL,
        logger:    defaultLogger,
        callbacks: DefaultCallback,
        dialect:   newDialect(dialect, dbSQL),
    }
    db.parent = db
    if err != nil {
        return
    }
    // Send a ping to make sure the database connection is alive.
    if d, ok := dbSQL.(*sql.DB); ok {
        if err = d.Ping(); err != nil && ownDbSQL {
            d.Close()
        }
    }
    return
}

gorm.Open 有兩個參數(shù), 一個是數(shù)據(jù)庫名稱, 其余是連接參數(shù).

switch 語句中, 可以發(fā)現(xiàn)如果第一個參數(shù)是 string 類型, 實際上是通過 Golang 中的 sql 模塊連接:

dbSQL, err = sql.Open(driver, source)

也可以直接傳遞一個實現(xiàn)了 SQLCommon 接口的實例.

然后初始化了一個 gorm.DB 實例, 并在最后執(zhí)行了一次 ping 請求, 測試數(shù)據(jù)庫連接是否正常.

看一下 gorm.DB 結(jié)構(gòu)體:

// DB contains information for current db connection
type DB struct {
    sync.RWMutex
    Value        interface{}
    Error        error
    RowsAffected int64

    // single db
    db                SQLCommon
    blockGlobalUpdate bool
    logMode           logModeValue
    logger            logger
    search            *search
    values            sync.Map

    // global db
    parent        *DB
    callbacks     *Callback
    dialect       Dialect
    singularTable bool

    // function to be used to override the creating of a new timestamp
    nowFuncOverride func() time.Time
}

gorm.DB 擴(kuò)展自 sync.RWMutex 讀寫互斥鎖.

gorm.DB

上面已經(jīng)看過了 gorm.DB 結(jié)構(gòu)體的定義了, 從入門的示例代碼中可以看出, 所有的操作都是圍繞它來進(jìn)行的,
所以 gorm.DB 是核心的結(jié)構(gòu)體. 看下它具體實現(xiàn)了哪些方法.

// New clone a new db connection without search conditions
func (s *DB) New() *DB {
    clone := s.clone()
    clone.search = nil
    clone.Value = nil
    return clone
}

type closer interface {
    Close() error
}

// Close close current db connection.  If database connection is not an io.Closer, returns an error.
func (s *DB) Close() error {
    if db, ok := s.parent.db.(closer); ok {
        return db.Close()
    }
    return errors.New("can't close current db")
}

克隆數(shù)據(jù)庫的連接和關(guān)閉數(shù)據(jù)庫連接. New 方法內(nèi)部使用到了 s.clone(),

func (s *DB) clone() *DB {
    db := &DB{
        db:                s.db,
        parent:            s.parent,
        logger:            s.logger,
        logMode:           s.logMode,
        Value:             s.Value,
        Error:             s.Error,
        blockGlobalUpdate: s.blockGlobalUpdate,
        dialect:           newDialect(s.dialect.GetName(), s.db),
        nowFuncOverride:   s.nowFuncOverride,
    }

    s.values.Range(func(k, v interface{}) bool {
        db.values.Store(k, v)
        return true
    })

    if s.search == nil {
        db.search = &search{limit: -1, offset: -1}
    } else {
        db.search = s.search.clone()
    }

    db.search.db = db
    return db
}

略過一些簡單的 get/set 方法, 接著看

// NewScope create a scope for current operation
func (s *DB) NewScope(value interface{}) *Scope {
    dbClone := s.clone()
    dbClone.Value = value
    scope := &Scope{db: dbClone, Value: value}
    if s.search != nil {
        scope.Search = s.search.clone()
    } else {
        scope.Search = &search{}
    }
    return scope
}

NewScope 會為當(dāng)前的操作創(chuàng)建一個新的 scope (作用域).

// QueryExpr returns the query as expr object
func (s *DB) QueryExpr() *expr {
    scope := s.NewScope(s.Value)
    scope.InstanceSet("skip_bindvar", true)
    scope.prepareQuerySQL()

    return Expr(scope.SQL, scope.SQLVars...)
}

// SubQuery returns the query as sub query
func (s *DB) SubQuery() *expr {
    scope := s.NewScope(s.Value)
    scope.InstanceSet("skip_bindvar", true)
    scope.prepareQuerySQL()

    return Expr(fmt.Sprintf("(%v)", scope.SQL), scope.SQLVars...)
}

QueryExprSubQuery 都用到了 NewScope, 在當(dāng)前的作用域下獲取查詢表達(dá)式和進(jìn)行子查詢.

接著是很多查詢方法, 類似 Where,

// Where return a new relation, filter records with given conditions, accepts `map`, `struct` or `string` as conditions, refer http://jinzhu.github.io/gorm/crud.html#query
func (s *DB) Where(query interface{}, args ...interface{}) *DB {
    return s.clone().search.Where(query, args...).db
}

跳過這些方法, 等后面探究查詢表達(dá)式的時候再詳細(xì)研究.

// Scopes pass current database connection to arguments `func(*DB) *DB`, which could be used to add conditions dynamically
//     func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
//         return db.Where("amount > ?", 1000)
//     }
//
//     func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {
//         return func (db *gorm.DB) *gorm.DB {
//             return db.Scopes(AmountGreaterThan1000).Where("status in (?)", status)
//         }
//     }
//
//     db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// Refer https://jinzhu.github.io/gorm/crud.html#scopes
func (s *DB) Scopes(funcs ...func(*DB) *DB) *DB {
    for _, f := range funcs {
        s = f(s)
    }
    return s
}

Scopes 是一個鉤子函數(shù), 用于動態(tài)添加查詢條件, 這在函數(shù)是一等公民的語言里是一個常見的模式.

事務(wù)實現(xiàn)

看一下事務(wù)是如何實現(xiàn)的:

// Begin begins a transaction
func (s *DB) Begin() *DB {
    return s.BeginTx(context.Background(), &sql.TxOptions{})
}

// BeginTx begins a transaction with options
func (s *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) *DB {
    c := s.clone()
    if db, ok := c.db.(sqlDb); ok && db != nil {
        tx, err := db.BeginTx(ctx, opts)
        c.db = interface{}(tx).(SQLCommon)

        c.dialect.SetDB(c.db)
        c.AddError(err)
    } else {
        c.AddError(ErrCantStartTransaction)
    }
    return c
}

這一部分是開始事務(wù)時的操作, 實際上是 c.db 實現(xiàn)了 sqlDb 接口, 調(diào)用了 BeginTx 方法.

type sqlDb interface {
    Begin() (*sql.Tx, error)
    BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
}

接著看如何提交事務(wù):

// Commit commit a transaction
func (s *DB) Commit() *DB {
    var emptySQLTx *sql.Tx
    if db, ok := s.db.(sqlTx); ok && db != nil && db != emptySQLTx {
        s.AddError(db.Commit())
    } else {
        s.AddError(ErrInvalidTransaction)
    }
    return s
}

和開始事務(wù)類似, s.db 實現(xiàn)了 sqlTx 接口, 調(diào)用了 Commit 方法.

type sqlTx interface {
    Commit() error
    Rollback() error
}

sqlTx 接口里還有個 Rollback 方法, 所以回滾操作也是類似的:

// Rollback rollback a transaction
func (s *DB) Rollback() *DB {
    var emptySQLTx *sql.Tx
    if db, ok := s.db.(sqlTx); ok && db != nil && db != emptySQLTx {
        if err := db.Rollback(); err != nil && err != sql.ErrTxDone {
            s.AddError(err)
        }
    } else {
        s.AddError(ErrInvalidTransaction)
    }
    return s
}

// RollbackUnlessCommitted rollback a transaction if it has not yet been
// committed.
func (s *DB) RollbackUnlessCommitted() *DB {
    var emptySQLTx *sql.Tx
    if db, ok := s.db.(sqlTx); ok && db != nil && db != emptySQLTx {
        err := db.Rollback()
        // Ignore the error indicating that the transaction has already
        // been committed.
        if err != sql.ErrTxDone {
            s.AddError(err)
        }
    } else {
        s.AddError(ErrInvalidTransaction)
    }
    return s
}

RollbackUnlessCommittedRollback 的區(qū)別在于前者少了一個 err != nil 的判斷,
看了半天還是難以理解這有什么差別.

RollbackUnlessCommitted 作者給出的例子如下:

func doTransaction(DB *gorm.DB) error {
  tx := DB.Begin()
  defer tx.RollbackUnlessCommitted()

  u := User{Name: "test"}
  if err != tx.Save(&User).Error; err != nil {
    return err
  }
  return tx.Commit().Error
}

相比較而言, 官方文檔上事務(wù)的例子如下:

func CreateAnimals(db *gorm.DB) error {
  // 請注意,事務(wù)一旦開始,你就應(yīng)該使用 tx 作為數(shù)據(jù)庫句柄
  tx := db.Begin()
  defer func() {
    if r := recover(); r != nil {
      tx.Rollback()
    }
  }()

  if err := tx.Error; err != nil {
    return err
  }

  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    tx.Rollback()
    return err
  }

  if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
    tx.Rollback()
    return err
  }

  return tx.Commit().Error
}

總結(jié)

暫時就看到這里吧, gorm.DB 還有很多方法等待后續(xù)發(fā)掘.

主要看了數(shù)據(jù)庫的連接過程, 基本是通過 dbSQL, err = sql.Open(driver, source) 實現(xiàn)的.

也看了事務(wù)部分, 主要是要實現(xiàn)兩個接口:

type sqlDb interface {
    Begin() (*sql.Tx, error)
    BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
}

type sqlTx interface {
    Commit() error
    Rollback() error
}

當(dāng)然, 對于其中的 RollbackUnlessCommittedRollback 有點疑惑, 因為我想不明白到底有什么不同.

既然是 ORM, 模型定義應(yīng)該是重中之重, 后續(xù)將探索 Model 實現(xiàn).

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 當(dāng)夜幕降臨, 遠(yuǎn)方的你,是否也會彷徨? 當(dāng)夜幕降臨, 天涯某處的你,是否也會迷茫? 可是, 親愛的你呀~ 何必彷徨...
    風(fēng)尖刃閱讀 378評論 0 0
  • 清明節(jié)掃墓是一項歷史悠久的傳統(tǒng)習(xí)俗,也是緬懷先人的一種方式。祭祀先人是一種很肅穆很莊嚴(yán)的儀式。 ?掃墓要選擇好時間...
    黃德有閱讀 526評論 0 1
  • 今天不上學(xué),可是上輔導(dǎo)班,今天就沒有上學(xué)時那么急,七點半送去就好,我把孩子送去,昨晚給趙樂捎的卷子忘拿了,就又回來...
    2018級高佳浩媽媽閱讀 247評論 0 3

友情鏈接更多精彩內(nèi)容