go 源码合集 -- Gorm主逻辑代码解析

Gorm主逻辑代码解析

参考资料:

img

示例代码

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

img

img

代码走读分析 重点

# 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值