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{}) // 创建 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) }
数据库连接
从 gorm.Open
开始看起吧, 看数据库是怎么连接的:
// 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
有两个参数, 一个是数据库名称, 其余是连接参数.
从 switch
语句中, 可以发现如果第一个参数是 string 类型, 实际上是通过 Golang 中的 sql
模块连接:
dbSQL, err = sql.Open(driver, source)
也可以直接传递一个实现了 SQLCommon
接口的实例.
然后初始化了一个 gorm.DB
实例, 并在最后执行了一次 ping 请求, 测试数据库连接是否正常.
看一下 gorm.DB
结构体:
// 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
扩展自 sync.RWMutex
读写互斥锁.
gorm.DB
上面已经看过了 gorm.DB
结构体的定义了, 从入门的示例代码中可以看出, 所有的操作都是围绕它来进行的,
所以 gorm.DB
是核心的结构体. 看下它具体实现了哪些方法.
// 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") }
克隆数据库的连接和关闭数据库连接. New
方法内部使用到了 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
会为当前的操作创建一个新的 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
, 在当前的作用域下获取查询表达式和进行子查询.
接着是很多查询方法, 类似 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 }
跳过这些方法, 等后面探究查询表达式的时候再详细研究.
// 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
是一个钩子函数, 用于动态添加查询条件, 这在函数是一等公民的语言里是一个常见的模式.
事务实现
看一下事务是如何实现的:
// 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 }
这一部分是开始事务时的操作, 实际上是 c.db
实现了 sqlDb
接口, 调用了 BeginTx
方法.
type sqlDb interface { Begin() (*sql.Tx, error) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) }
接着看如何提交事务:
// 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 }
和开始事务类似, s.db
实现了 sqlTx
接口, 调用了 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
的区别在于前者少了一个 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 }
相比较而言, 官方文档上事务的例子如下:
func CreateAnimals(db *gorm.DB) error { // 请注意,事务一旦开始,你就应该使用 tx 作为数据库句柄 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 }
总结
暂时就看到这里吧, gorm.DB
还有很多方法等待后续发掘.
主要看了数据库的连接过程, 基本是通过 dbSQL, err = sql.Open(driver, source)
实现的.
也看了事务部分, 主要是要实现两个接口:
type sqlDb interface { Begin() (*sql.Tx, error) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) } type sqlTx interface { Commit() error Rollback() error }
当然, 对于其中的 RollbackUnlessCommitted
和 Rollback
有点疑惑, 因为我想不明白到底有什么不同.
既然是 ORM, 模型定义应该是重中之重, 后续将探索 Model 实现.