8

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

 2 years 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.
neoserver,ios ssh client

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

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


Recommend

  • 37
    • www.tuicool.com 5 years ago
    • Cache

    将数据库转换为gorm结构

    gorm-tools gorm mysql数据库转 struct 工具,可以将mysql数据库自动生成golang sturct结构,带大驼峰命名规则。...

  • 3
    • wnanbei.github.io 3 years ago
    • Cache

    Go Gorm 数据库连接与模型

    对开发者友好的 Go ORM 库,v2 版本。 go get -u gorm.io/gorm import "gorm.io/gorm" 连接数据库 数据库驱动 Gorm 官方支...

  • 8

    对于后台开发新的需求时,一般会先进行各种表的设计,写各个表的建表语句 然后根据建立的表,写对应的model代码、基础的增删改查代码(基础的增删改查服务可以划入DAO(Data Access Object)层)。 model代码都有一些固定的格式,可以通过解析SQL建表语...

  • 12
    • www.cnblogs.com 2 years ago
    • Cache

    MySQL InnoDB缓存 - Amos01

    对于各种用户数据、索引数据等各种数据都是需要持久化存储到磁盘,然后以“页”为单位进行读写。 相对于直接读写缓存,磁盘IO的成本相当高昂。 对于读取的页面数据,并不是使用完就释放掉,而是放到缓冲区,因为下一次操作有可能还需要读区该页面。...

  • 4

    1. 什么是反射 反射是程序在运行期间获取变量的类型和值、或者执行变量的方法的能力。 Golang反射包中有两对非常重要的函数和类型,两个函数分别是: reflect.TypeOf...

  • 5
    • www.cnblogs.com 2 years ago
    • Cache

    Golang反射修改变量值 - Amos01

    前面的随笔Golang反射获取变量类型和值分享了如何通过反射获取变量的类型和值, 也就是

  • 9
    • www.cnblogs.com 2 years ago
    • Cache

    Gorm源码学习-创建行记录 - Amos01

    Gorm源码学习系列 Gorm源码学习-数据库连接 此文是Gorm源码学习系列的第二篇,主要梳理下通过Gorm创建表的流程。 2. 创...

  • 7
    • www.cnblogs.com 2 years ago
    • Cache

    Gin使用及源码简析 - Amos01

    1. Gin简介 前面通过两篇文章分享了Golang HTTP编程的路由分发、请求/响应处理。 可以看出来Golang原生HTTP编程在路由分组、动态路由及参数读取/验证、构造String/Data/JSON/HTML响应的方法等存在优化的空间。 Gin是一个用Golang编写的高性...

  • 4
    • www.cnblogs.com 1 year ago
    • Cache

    RabbitMQ系列-概念及安装 - Amos01

    1. 消息队列 消息队列是指利用队列这种数据结构进行消息发送、缓存、接收,使得进程间能相互通信,是点对点的通信 而消息代理是对消息队列的扩展,支持对消息的路由,是发布-订阅模式的通信,消息的发送者并不清楚消息的接收者,消息可以被多个消费...

  • 7
    • www.cnblogs.com 1 year ago
    • Cache

    RabbitMQ系列-Exchange介绍 - Amos01

    RabbitMQ系列 RabbitMQ系列-概念及安装  1. Exchange

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK