Gorm源码学习-数据库连接 - Amos01
source link: https://www.cnblogs.com/amos01/p/16890747.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
gorm源码地址: Gorm , 本文基于commit:cef3de694d9615c574e82dfa0b50fc7ea2816f3e
官方入门指南: Doc
2 连接数据库代码示例
目前Gorm官方支持的数据库类型有:MySQL, PostgreSQL, SQLite, SQL Server.
目前Go官方支持MySQL驱动,代码地址:mysql-driver
下面来看连接MySQL的数据库的基本代码
package main import ( "fmt" "time" "gorm.io/driver/mysql" "gorm.io/gorm") func main() { // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情 dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?timeout=%s&readTimeout=%s&writeTimeout=%s", "root", "zbwmysql", "127.0.0.1", "3306", "user_db", "100ms", "2s", "3s") db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { fmt.Printf("gorm open fail, err:%v dsn:%v\n", err, dsn) } mysqlDB, err := db.DB() if err != nil { fmt.Printf("get mysql db fail, err:%v\n", err) } // 参考 https://github.com/go-sql-driver/mysql#important-settings 获取详情 mysqlDB.SetConnMaxLifetime(time.Minute * 3) // 客户端将空闲连接主动断开的超时时间,官方建议小于5分钟 mysqlDB.SetMaxOpenConns(10) // 取决于服务器的配置 mysqlDB.SetMaxIdleConns(10) // 官方建议和SetMaxOpenConns相同}
这里有必要看下 timeout
,readTimeout
,writeTimeout
,SetConnMaxLifetime
三个参数
timeout
是指 建立连接的一个超时时间
readTimeout
是指 I/O 读操作的超时时间
writeTimeout
是指 I/O 写操作的超时时间
SetConnMaxLifetime
是指客户端将空闲连接主动断开的超时时间,
如果设置为0,则连接池的连接将这一直被复用,但是系统会主动将长时间的连接杀掉,
因此若客户端再次使用长时间空闲的连接将会报错,driver: bad connection
,具体如下
问题的修复记录,可以看 issues-1120
3 连接数据库代码分析
从上一节看,Gorm连接数据库的过程只需要调用一个函数,
func Open(dialector Dialector, opts ...Option) (db *DB, err error)
但是Gorm目前是MySQL, PostgreSQL, SQLite, SQL Server,四种类型的数据库的,这个是怎么做到的呢?
这就需要具体看下请求参数Dialector
和 返回参数DB
及Open
函数内部实现
首先让我们先看看Golang的interface
类型
3.1 interface
理解
An interface type is defined as a set of method signatures.
A value of interface type can hold any value that implements those methods.
A type implements an interface by implementing its methods. There is no explicit declaration of intent, no "implements" keyword.
以上摘抄自A Tour of Go , interface
是一种包含方法定义的类型,通过实现该interface
的所有方法来隐式实现该接口。
3.2 Dialector
接口定义
Dialector
定义如下,这里对部分方法加了注释,方便理解。
// Dialector GORM database dialectortype Dialector interface { Name() string // 驱动名称 Initialize(*DB) error // 初始化连接 Migrator(db *DB) Migrator DataTypeOf(*schema.Field) string // 类型映射 DefaultValueOf(*schema.Field) clause.Expression // 类型默认值 BindVarTo(writer clause.Writer, stmt *Statement, v interface{}) QuoteTo(clause.Writer, string) Explain(sql string, vars ...interface{}) string // SQL语句格式化输出}
不仅仅Mysql驱动,PostgreSQL, SQLite, SQL Server驱动都得实现Dialector
中定义的全部方法。
并且这些方法恰恰是不同数据库的区别所在,比如不同数据库的数据类型是有差异的,即使含义相同,写法也可能不同,
因此gorm的数据类型映射到不同数据库能识别的类型,这就是DataTypeOf
实现的功能。
可以在go-gorm找到各种数据库的Dialector实现,如PostgreSQL Dialector
,MySQL Dialector
3.3 DB
结构体定义
DB
定义如下,这里对部分方法加了注释,方便理解。
// DB GORM DB definitiontype DB struct { *Config // 连接及其连接相关信息等 Error error RowsAffected int64 Statement *Statement // SQL语句执行相关信息 clone int}
其中,Config
中会保留连接ConnPool
、CRUD相关的函数callbacks
等信息,部分代码代码如下,完整代码见gorm.Config
// Config GORM configtype Config struct { // ClauseBuilders clause builder ClauseBuilders map[string]clause.ClauseBuilder // ConnPool db conn pool ConnPool ConnPool // Dialector database dialector Dialector // Plugins registered plugins Plugins map[string]Plugin callbacks *callbacks cacheStore *sync.Map}
看了Open
函数的请求参数和返回参数,接下来我们看看内部的具体实现
3.3 Gorm.Open
实现分析
Gorm.Open
完整代码可以在Github上看到。这里重点关注两个地方
- 注册CRUD的回调函数,
db.callbacks = initializeCallbacks(db)
,具体实现如下
func initializeCallbacks(db *DB) *callbacks { return &callbacks{ processors: map[string]*processor{ "create": {db: db}, "query": {db: db}, "update": {db: db}, "delete": {db: db}, "row": {db: db}, "raw": {db: db}, }, }}
- 调用具体函数的初始化方法
if config.Dialector != nil { err = config.Dialector.Initialize(db) }
这里会根据具体Dialector
的具体值调用对应的Initialize
方法。
如果Dialector
为 mysql.Open(dsn)
的返回值,那就会调用Gorm MySQL
驱动的Initialize
方法。
3.4MySQL Dialector
的 Initialize
方法实现分析
Initialize
主要干了两件事情,调用sql.Open
、注册CRUD的处理函数及对应的钩子函数。
钩子函数是在创建、查询、更新、删除等操作之前、之后调用的函数。
3.4.1 调用sql.Open,该函数可能只是校验下参数,并没有实际建立连接
db.ConnPool, err = sql.Open(dialector.DriverName, dialector.DSN)
其中,sql.Open
声明如下
func Open(driverName, dataSourceName string) (*DB, error)
db.ConnPool
是interface
类型,定义如下
// ConnPool db conns pool interfacetype 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}
sql.DB
是结构体类型,gorm.ConnPool
是 interface
类型,因此sql.DB
实现了gorm.ConnPool
定义的四个方法,因此CRUD操作会通过gorm.ConnPool
调用到sql.DB
实现的这四个函数实现。
这里也应证了前面的说明
A value of interface type can hold any value that implements those methods.
通过看源码,sql.Conn
和sql.Tx
也实现了gorm.ConnPool
定义的四个方法。
3.4.2 注册CRUD相关函数,这里只截取callbacks.RegisterDefaultCallbacks
的部分实现。
func RegisterDefaultCallbacks(db *gorm.DB, config *Config) { 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}
在gorm.Open的过程中注册了创建记录时的回调函数createCallback.Register("gorm:create", Create(config))
具体的细节在后续章节展开,这里就细说。
此外,从代码可以看出,这里注册了在创建操作之前、之后调用的钩子方法。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK