Gorm源码学习-数据库连接

1 前言

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,具体如下

Gorm源码学习-数据库连接

问题的修复记录,可以看 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 Go , interface是一种包含方法定义的类型,通过实现该 interface的所有方法来隐式实现该接口。

3.2 Dialector 接口定义

<a href="https://github.com/go-gorm/gorm/blob/master/interfaces.go#L12" rel="noopener">Dialector</a>定义如下,这里对部分方法加了注释,方便理解。

// Dialector GORM database dialector
type Dialector interface {
    Name() string // &#x9A71;&#x52A8;&#x540D;&#x79F0;
    Initialize(*DB) error // &#x521D;&#x59CB;&#x5316;&#x8FDE;&#x63A5;
    Migrator(db *DB) Migrator
    DataTypeOf(*schema.Field) string // &#x7C7B;&#x578B;&#x6620;&#x5C04;
    DefaultValueOf(*schema.Field) clause.Expression // &#x7C7B;&#x578B;&#x9ED8;&#x8BA4;&#x503C;
    BindVarTo(writer clause.Writer, stmt *Statement, v interface{})
    QuoteTo(clause.Writer, string)
    Explain(sql string, vars ...interface{}) string // SQL&#x8BED;&#x53E5;&#x683C;&#x5F0F;&#x5316;&#x8F93;&#x51FA;
}

不仅仅Mysql驱动,PostgreSQL, SQLite, SQL Server驱动都得实现 Dialector中定义的全部方法。

并且这些方法恰恰是不同数据库的区别所在,比如不同数据库的数据类型是有差异的,即使含义相同,写法也可能不同,

因此gorm的数据类型映射到不同数据库能识别的类型,这就是 DataTypeOf实现的功能。

可以在go-gorm找到各种数据库的Dialector实现,如PostgreSQL DialectorMySQL Dialector

3.3 DB 结构体定义

<a href="https://github.com/go-gorm/gorm/blob/master/gorm.go#L89" rel="noopener">DB</a>定义如下,这里对部分方法加了注释,方便理解。

// DB GORM DB definition
type DB struct {
    *Config // &#x8FDE;&#x63A5;&#x53CA;&#x5176;&#x8FDE;&#x63A5;&#x76F8;&#x5173;&#x4FE1;&#x606F;&#x7B49;
    Error        error
    RowsAffected int64
    Statement    *Statement // SQL&#x8BED;&#x53E5;&#x6267;&#x884C;&#x76F8;&#x5173;&#x4FE1;&#x606F;
    clone        int
}

其中,Config中会保留连接 ConnPool 、CRUD相关的函数 callbacks等信息,部分代码代码如下,完整代码见gorm.Config

// Config GORM config
type 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的回调函数, <a href="https://github.com/go-gorm/gorm/blob/master/gorm.go#L170" rel="noopener">db.callbacks = initializeCallbacks(db)</a>,具体实现如下
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方法。

如果 Dialectormysql.Open(dsn)的返回值,那就会调用 Gorm MySQL驱动的 Initialize方法。

3.4 MySQL DialectorInitialize 方法实现分析

Initialize 主要干了两件事情,调用 sql.Open、注册CRUD的处理函数及对应的钩子函数。

钩子函数是在创建、查询、更新、删除等操作之前、之后调用的函数。

3.4.1 调用sql.Open ,该函数可能只是校验下参数,并没有实际建立连接

db.ConnPool, err = sql.Open(dialector.DriverName, dialector.DSN)

其中, <a href="https://github.com/golang/go/blob/master/src/database/sql/sql.go#L813" rel="noopener">sql.Open</a>声明如下

func Open(driverName, dataSourceName string) (*DB, error)

db.ConnPoolinterface类型,定义如下

// ConnPool db conns pool interface
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
}

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定义的四个方法。

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))<br>具体的细节在后续章节展开,这里就细说。

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

Original: https://www.cnblogs.com/amos01/p/16890747.html
Author: Amos01
Title: Gorm源码学习-数据库连接

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/798170/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球