5

Gorm源码学习-数据库连接 - Amos01

 1 year ago
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相同}

这里有必要看下 timeoutreadTimeoutwriteTimeoutSetConnMaxLifetime 三个参数

timeout是指 建立连接的一个超时时间

readTimeout是指 I/O 读操作的超时时间

writeTimeout是指 I/O 写操作的超时时间

SetConnMaxLifetime 是指客户端将空闲连接主动断开的超时时间,

如果设置为0,则连接池的连接将这一直被复用,但是系统会主动将长时间的连接杀掉,

因此若客户端再次使用长时间空闲的连接将会报错,driver: bad connection,具体如下

2309259-20221114232212417-220572160.png

问题的修复记录,可以看 issues-1120 

 3 连接数据库代码分析

从上一节看,Gorm连接数据库的过程只需要调用一个函数,

func Open(dialector Dialector, opts ...Option) (db *DB, err error)

 但是Gorm目前是MySQL, PostgreSQL, SQLite, SQL Server,四种类型的数据库的,这个是怎么做到的呢?

 这就需要具体看下请求参数Dialector 和 返回参数DBOpen函数内部实现

 首先让我们先看看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 Gointerface是一种包含方法定义的类型,通过实现该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 DialectorMySQL 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上看到。这里重点关注两个地方

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 DialectorInitialize 方法实现分析

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.ConnPoolinterface类型,定义如下

// 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.ConnPoolinterface类型,因此sql.DB实现了gorm.ConnPool定义的四个方法,因此CRUD操作会通过gorm.ConnPool调用到sql.DB实现的这四个函数实现。

这里也应证了前面的说明

A value of interface type can hold any value that implements those methods.

通过看源码sql.Connsql.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))
具体的细节在后续章节展开,这里就细说。

此外,从代码可以看出,这里注册了在创建操作之前、之后调用的钩子方法。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK