Gorm主逻辑代码解析
参考资料:
示例代码
package main
import (
"context"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
"time"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"column:name"`
Birthday time.Time `gorm:"column:birthday"`
}
func main() {
dsn := "root:12345678@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
err = db.AutoMigrate(&User{})
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
user := User{
Name: "user5",
}
birthdayStr := "1988-11-20"
birthday, err := time.Parse("2006-01-02", birthdayStr)
if err != nil {
log.Fatal(err)
}
user.Birthday = birthday
result := db.WithContext(ctx).Create(&user)
if result.Error != nil {
log.Fatal(result.Error)
}
fmt.Printf("Inserted user with ID: %d\n", user.ID)
}
代码走读分析 重点
# db 链接建立
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
=>Open(dialector Dialector, opts ...Option) (db *DB, err error)
=> range opts->gorm.config->config.set[...]->
=> db = &DB{Config: config, clone: 1}->db.callbacks = initializeCallbacks(db)
=> config.Dialector.Initialize(db)
=> 设置连接池ConnPool
=>mysql.Initialize()->db.ConnPool, err = sql.Open(dialector.DriverName, dialector.DSN)
=>database/sql.open()
=>go-sql-driver/mysql::driveri(init())
=>OpenConnector(dsn string) (driver.Connector, error)->mysql::connector{config}
=>OpenDB(c driver.Connector) *DB ->go db.connectionOpener(ctx)->DB{connector}
=> db.ConnPool.QueryRowContext(ctx, "SELECT VERSION()").Scan(&dialector.ServerVersion)
-> database.sql::(db *DB) QueryRowContext(ctx, query string, args ...any) *Row
->rows, err := db.QueryContext(ctx, query, args...)
-> rows, err = db.query(ctx, query, args, cachedOrNewConn)
-> dc, err := db.conn(ctx, strategy)
真正的赋予持有ConnPool的sql 操作 tcp连接
没有可用连接会创建链接
-> ci, err := db.connector.Connect(ctx)
-> nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr) 建立 socket 链接
-> return &driverConn{ci,db}
=>callbacks.RegisterDefaultCallbacks(db, callbackConfig) 注册 mysql 增删改查等操作的函数链
=> db.Callback().Create().Register("gorm:create", Create(config))
=> db.Callback().Query().Register("gorm:query", Query)
=> db.Callback().Delete().Register("gorm:delete", Delete(config))...
=> Register 注册到db.callbacks中
-> c.processor.callbacks = append(c.processor.callbacks, c{name,handler(fn)})
-> c.processor.compile()-> p.fns, err = sortCallbacks(p.callbacks)排序后放入 fns
=> if SkipInitializeWithVersion 更新基础配置
# 插入数据
result := db.WithContext(ctx).Create(&user)
=>db.Session(&Session{Context: ctx})->根据Session设置txConfig
-> gorm.DB{txConfig数据库操作信息,Statement查询语句,RowsAffected结果,Error}
=>(db *DB) Create(value interface{}存放结果的内存地址) (tx *DB)
-> tx.Statement.Dest = value->tx.callbacks.Create().Execute(tx)
-> 走到上面设置的操作函数链
-> cs.processors["create"]::processor{db,fns,Clauses,callback}->Execute(tx)
=> (p *processor) Execute(db *DB) *DB
-> stmt.Model = stmt.Dest 设置输出的目标结构
-> stmt.Parse(stmt.Model) 解析目标结构
-> for f <- range p.fns:[f(db)]->执行之前注册的gorm:create函数,并把操作结果置入 db 结构内
-> db.Logger.Trace
# gorm:create.Create(config)的执行
Create(config *Config) func(db *gorm.DB)
=> 判断是否有需要补充的 Clause
=> db.Statement.AddClause(c)
=> rows, err := db.Statement.ConnPool.QueryContext(
db.Statement.Context数据库操作上下文, db.Statement.SQL.String(), db.Statement.Vars...,)
=> gorm.ConnPool 设置四个接口函数[PrepareContext,ExecContext,QueryContext,QueryRowContext]
->之前sql.Open开启的连接池持有的连接实现该四个函数
-> func (tx *Tx) ExecContext(ctx, query string, args ...any) (Result, error)
->(db *DB) execDC(ctx dc *driverConn, release func(error), query string, args []any)
->resultFromStatement(ctx, dc.ci, ds, args...) 处理stmt 内的参数
-> ctxDriverStmtExec -> siCtx.ExecContext(ctx, nvdargs)
-> 走到 go-sql-driver/mysql
-> (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue)
=> stmt.Exec(dargs)
->err := stmt.writeExecutePacket(args) 执行 sql
->mc.readResultSetHeaderPacket() 读返回结果
-> data, err := mc.readPacket()
# 如果是查询QueryContext
=> (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error)
-> mc.watchCancel(ctx) 设置超时监控 channel :: mc.watcher <- ctx
-> rows, err := mc.query(query, dargs)
=>(mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error)
-> err := mc.writeCommandPacketStr(comQuery, query) 连接写入sql,tcp 发送包
-> rows := new(textRows) 开辟存储结构的内存空间,类型是textRows
-> rows.rs.columns, err = mc.readColumns(resLen) 从 tcp 二进制流读结果到rows
-> return rows
=> rows->return driver.Rows
=> gorm.Scan(rows, db, mode) 将查询结构 rows 放入 Scan函数返回
=> Scan(rows Rows, db *DB, mode ScanMode)
-> switch dest := db.Statement.Dest.(type) 根据 stmt 的 dest 类型设置怎么读 rows
-> rows.Next()-> db.RowsAffected++ -> rows.Scan(values...)
=> 类比原始 sql 操作
for rows.Next() {
err := rows.Scan(&id, &name)
if err != nil {
log.Fatal(err)
}
log.Println(id, name)
}
# mysql driver 连接的处
# 在多个方法如(*DB)[PrepareContext,ExecContext,QueryContext,QueryRowContext]等
# 都会调用db.conn获取真正的 tcp连接
=> (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error)
=> 1.查看是否有空闲连接,直接返回 conn
-> last := len(db.freeConn) - 1
-> return db.freeConn[last]
=> 2. 如果打开的链接过多,只能等待空闲连接放回,通过 等待的chan内放入 可用conn 结构
-> if db.maxOpen > 0 && db.numOpen >= db.maxOpen
-> db.connRequests[reqKey] = req(make(chan connRequest, 1))
-> select 阻塞等待 成功等到则返回链接
-> case ret, ok := <-req: return ret.conn, ret.err
=> 3.可以创建新的连接
-> ci, err := db.connector.Connect(ctx) 创建真正的 tcp 数据库连接,修改对应状态
-> 如果发生错误 -> db.maybeOpenNewConnections()会尝试重新获取链接
=>maybeOpenNewConnections()
=> 如果有等待处理的请求,向db.openerCh <- struct{}{}
=> 激活监听的go db.connectionOpener(ctx) 协程创建新的连接
=> 4.只要连接被使用完调用putConnDBLocked或者putConn(最终调的还是putConnDBLocked)
=>(db *DB) putConnDBLocked(dc *driverConn, err error)
-> 假如有等待请求 for reqKey, req = range db.connRequests
枚举一个等待的请求的 chan 传入新建的mysqlconn
-> db.freeConn = append(db.freeConn, dc)
加入到空闲队列,并检查是否需要释放多余连接
# 监听 chann 创建新连接的协程
go db.connectionOpener(ctx)
=> 循环监听 db.openerCh,执行db.openNewConnection(ctx)
=> (db *DB) openNewConnection(ctx context.Context)
-> ci, err := db.connector.Connect(ctx) 真正的 tcp 连接
-> &driverConn{ci,db} 通过putConnDBLocked()发送给排队等待连接的查询请求
-> db.putConnDBLocked(dc, err)
=>(db *DB) putConnDBLocked(dc *driverConn, err error)
-> 假如有等待请求 for reqKey, req = range db.connRequests
枚举一个等待的请求的 chan 传入新建的mysqlconn
-> db.freeConn = append(db.freeConn, dc)
加入到空闲队列,并检查是否需要释放多余连接
-> db.addDepLocked(dc, dc)
细节代码注释
Dialector 数据库连接器
// Dialector GORM database dialector
type Dialector interface {
Name() string // 连接器名称
Initialize(*DB) error // 连接器初始化连接方法
Migrator(db *DB) Migrator // 数据库迁移方法
DataTypeOf(*schema.Field) string // 类中每个字段的类型对应到 sql 语句
DefaultValueOf(*schema.Field) clause.Expression // 每个字段的默认值对应到 sql 语句
BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) // 使用预编译模式的时候使用
QuoteTo(clause.Writer, string) // 将类中的注释对应到 sql 语句中
Explain(sql string, vars ...interface{}) string // 将有占位符的 sql 解析为无占位符 sql,常用于日志打印等
}
// Open initialize db session based on dialector
// Open 初始化 DB 的 Session
func Open(dialector Dialector, opts ...Option) (db *DB, err error) {
config := &Config{}
sort.Slice(opts, func(i, j int) bool {
_, isConfig := opts[i].(*Config)
_, isConfig2 := opts[j].(*Config)
return isConfig && !isConfig2
})
for _, opt := range opts {
if opt != nil {
// 先调用 Apply 初始化 复杂结构 Config: 对数据库配置 config 修改
if applyErr := opt.Apply(config); applyErr != nil {
return nil, applyErr
}
defer func(opt Option) {
// Open 最后结束后调用 AfterInitialize
if errr := opt.AfterInitialize(db); errr != nil {
err = errr
}
}(opt)
}
}
// 调用dialector的 applay 设置 用 dialector的结构设置Config
if d, ok := dialector.(interface{ Apply(*Config) error }); ok {
if err = d.Apply(config); err != nil {
return
}
}
// 设置命名策略,如表名
if config.NamingStrategy == nil {
config.NamingStrategy = schema.NamingStrategy{IdentifierMaxLength: 64} // Default Identifier length is 64
}
// 设置日志输出管道
if config.Logger == nil {
config.Logger = logger.Default
}
// 设置当前时间的获取方式函数
if config.NowFunc == nil {
config.NowFunc = func() time.Time { return time.Now().Local() }
}
// 设置连接器
if dialector != nil {
config.Dialector = dialector
}
// 设置插件
if config.Plugins == nil {
config.Plugins = map[string]Plugin{}
}
// 设置预处理的缓存
if config.cacheStore == nil {
config.cacheStore = &sync.Map{}
}
// 设置 gorm.DB结构
db = &DB{Config: config, clone: 1}
// 设置增删改查后的回调函数
db.callbacks = initializeCallbacks(db)
if config.ClauseBuilders == nil {
config.ClauseBuilders = map[string]clause.ClauseBuilder{}
}
if config.Dialector != nil {
err = config.Dialector.Initialize(db)
if err != nil {
if db, err := db.DB(); err == nil {
_ = db.Close()
}
}
}
if config.PrepareStmt {
preparedStmt := NewPreparedStmtDB(db.ConnPool)
db.cacheStore.Store(preparedStmtDBKey, preparedStmt)
db.ConnPool = preparedStmt
}
db.Statement = &Statement{
DB: db,
ConnPool: db.ConnPool,
Context: context.Background(),
Clauses: map[string]clause.Clause{},
}
if err == nil && !config.DisableAutomaticPing {
if pinger, ok := db.ConnPool.(interface{ Ping() error }); ok {
err = pinger.Ping()
}
}
if err != nil {
config.Logger.Error(context.Background(), "failed to initialize database, got error %v", err)
}
return
}
// Config 本身也可以作为一个 Option 在 Open 的第二个参数中出现
// Apply update config to new config
// 在 gorm config上执行该方法,是让参数 config 指向一个gorm.config
func (c *Config) Apply(config *Config) error {
if config != c {
*config = *c
}
return nil
}
// AfterInitialize initialize plugins after db connected
func (c *Config) AfterInitialize(db *DB) error {
if db != nil {
for _, plugin := range c.Plugins {
if err := plugin.Initialize(db); err != nil {
return err
}
}
}
return nil
}
// gorm 内部实现了一个 config
// 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
// gorm 的日志输出
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
// db 的具体连接
ConnPool ConnPool
// Dialector database dialector
// db 驱动器
Dialector
// Plugins registered plugins
Plugins map[string]Plugin
// 回调方法
callbacks *callbacks
cacheStore *sync.Map
}
connpool
可以直接使用 sql.DB的 conn 赋值给 ConnPool
// ConnPool db conns pool interface
// database/sql 的 sql.DB 结构实现了 Gorm 库的 ConnPool 接口
//ConnPool 实际上存放的就是 database/sql 的 sql.DB 结构
type ConnPool interface {
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}
// callbacks gorm callbacks manager
type callbacks struct {
processors map[string]*processor
}
// processor 处理器
type processor struct {
db *DB // 对应的 gorm.DB
Clauses []string // 处理器对应的 sql 片段
fns []func(*DB) // 这个处理器对应的处理函数
callbacks []*callback // 这个处理器对应的回调函数,生成 fns
}
回调函数的注册
func RegisterDefaultCallbacks(db *gorm.DB, config *Config) {
enableTransaction := func(db *gorm.DB) bool {
return !db.SkipDefaultTransaction
}
if len(config.CreateClauses) == 0 {
config.CreateClauses = createClauses
}
if len(config.QueryClauses) == 0 {
config.QueryClauses = queryClauses
}
if len(config.DeleteClauses) == 0 {
config.DeleteClauses = deleteClauses
}
if len(config.UpdateClauses) == 0 {
config.UpdateClauses = updateClauses
}
createCallback := db.Callback().Create()
createCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
createCallback.Register("gorm:before_create", BeforeCreate)
createCallback.Register("gorm:save_before_associations", SaveBeforeAssociations(true))
createCallback.Register("gorm:create", Create(config))
createCallback.Register("gorm:save_after_associations", SaveAfterAssociations(true))
createCallback.Register("gorm:after_create", AfterCreate)
createCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
createCallback.Clauses = config.CreateClauses
queryCallback := db.Callback().Query()
queryCallback.Register("gorm:query", Query)
queryCallback.Register("gorm:preload", Preload)
queryCallback.Register("gorm:after_query", AfterQuery)
queryCallback.Clauses = config.QueryClauses
deleteCallback := db.Callback().Delete()
deleteCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
deleteCallback.Register("gorm:before_delete", BeforeDelete)
deleteCallback.Register("gorm:delete_before_associations", DeleteBeforeAssociations)
deleteCallback.Register("gorm:delete", Delete(config))
deleteCallback.Register("gorm:after_delete", AfterDelete)
deleteCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
deleteCallback.Clauses = config.DeleteClauses
updateCallback := db.Callback().Update()
updateCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
updateCallback.Register("gorm:setup_reflect_value", SetupUpdateReflectValue)
updateCallback.Register("gorm:before_update", BeforeUpdate)
updateCallback.Register("gorm:save_before_associations", SaveBeforeAssociations(false))
updateCallback.Register("gorm:update", Update(config))
updateCallback.Register("gorm:save_after_associations", SaveAfterAssociations(false))
updateCallback.Register("gorm:after_update", AfterUpdate)
updateCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
updateCallback.Clauses = config.UpdateClauses
rowCallback := db.Callback().Row()
rowCallback.Register("gorm:row", RowQuery)
rowCallback.Clauses = config.QueryClauses
rawCallback := db.Callback().Raw()
rawCallback.Register("gorm:raw", RawExec)
rawCallback.Clauses = config.QueryClauses
}
走到 gorm.mysql包
对照思维导图和代码走读查看
func (dialector Dialector) Initialize(db *gorm.DB) (err error) {
if dialector.DriverName == "" {
dialector.DriverName = "mysql"
}
if dialector.DefaultDatetimePrecision == nil {
dialector.DefaultDatetimePrecision = &defaultDatetimePrecision
}
if dialector.Conn != nil {
db.ConnPool = dialector.Conn
} else {
db.ConnPool, err = sql.Open(dialector.DriverName, dialector.DSN)
if err != nil {
return err
}
}
withReturning := false
if !dialector.Config.SkipInitializeWithVersion {
err = db.ConnPool.QueryRowContext(context.Background(), "SELECT VERSION()").Scan(&dialector.ServerVersion)
if err != nil {
return err
}
if strings.Contains(dialector.ServerVersion, "MariaDB") {
dialector.Config.DontSupportRenameIndex = true
dialector.Config.DontSupportRenameColumn = true
dialector.Config.DontSupportForShareClause = true
dialector.Config.DontSupportNullAsDefaultValue = true
withReturning = checkVersion(dialector.ServerVersion, "10.5")
} else if strings.HasPrefix(dialector.ServerVersion, "5.6.") {
dialector.Config.DontSupportRenameIndex = true
dialector.Config.DontSupportRenameColumn = true
dialector.Config.DontSupportForShareClause = true
} else if strings.HasPrefix(dialector.ServerVersion, "5.7.") {
dialector.Config.DontSupportRenameColumn = true
dialector.Config.DontSupportForShareClause = true
} else if strings.HasPrefix(dialector.ServerVersion, "5.") {
dialector.Config.DisableDatetimePrecision = true
dialector.Config.DontSupportRenameIndex = true
dialector.Config.DontSupportRenameColumn = true
dialector.Config.DontSupportForShareClause = true
}
if strings.Contains(dialector.ServerVersion, "TiDB") {
dialector.Config.DontSupportRenameColumnUnique = true
}
}
// register callbacks
callbackConfig := &callbacks.Config{
CreateClauses: CreateClauses,
QueryClauses: QueryClauses,
UpdateClauses: UpdateClauses,
DeleteClauses: DeleteClauses,
}
if !dialector.Config.DisableWithReturning && withReturning {
callbackConfig.LastInsertIDReversed = true
if !utils.Contains(callbackConfig.CreateClauses, "RETURNING") {
callbackConfig.CreateClauses = append(callbackConfig.CreateClauses, "RETURNING")
}
if !utils.Contains(callbackConfig.UpdateClauses, "RETURNING") {
callbackConfig.UpdateClauses = append(callbackConfig.UpdateClauses, "RETURNING")
}
if !utils.Contains(callbackConfig.DeleteClauses, "RETURNING") {
callbackConfig.DeleteClauses = append(callbackConfig.DeleteClauses, "RETURNING")
}
}
callbacks.RegisterDefaultCallbacks(db, callbackConfig)
for k, v := range dialector.ClauseBuilders() {
db.ClauseBuilders[k] = v
}
return
}
关于 sql 包查看https://blog.csdn.net/u013010890/article/details/132587492