簡介
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...)
}
QueryExpr 和 SubQuery 都用到了 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
}
RollbackUnlessCommitted 和 Rollback 的區(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)然, 對于其中的 RollbackUnlessCommitted 和 Rollback 有點疑惑, 因為我想不明白到底有什么不同.
既然是 ORM, 模型定義應(yīng)該是重中之重, 后續(xù)將探索 Model 實現(xiàn).