成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

深入理解gorm是如何和數據庫建立連接的

開發 前端
在gorm.Open函數中,第一個參數是Dialector類型的參數,這是一個接口類型。也就是說只要實現了該接口,就能作為一個Dialector。這也就是gorm能夠針對很多數據庫進行操作的原因。

大家好,我是漁夫子。

本期和大家一起學習下gorm是如何和數據庫建立連接的。

一、gorm.Open

通常情況下,我們是通過gorm.Open函數就能在應用層和數據建立連接。如下:

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)

func main() {
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

在該代碼片段中,我們傳入了數據庫的用戶名、密碼、地址以及數據庫和數據庫對應的配置。然后通過gorm.Open函數就和數據庫建立連接了,gorm.Open函數返回的是一個gorm.DB對象。如下:

// DB GORM DB definition
type DB struct {
 *Config
 Error        error
 RowsAffected int64
 Statement    *Statement
 clone        int
}

在該數據結構中并沒有和數據庫連接相關的字段,那gorm.Open到底是如何和mysql數據庫建立連接的呢?我們繼續深入gorm.Open函數和mysql.Open函數的詳細內容。

二、gorm.Open函數

在gorm.Open函數中,傳入的參數是一個Dialector?接口類型的dialector變量。我們看到會將傳入的Dialector變量賦值給配置config.Dialector,如下:

config.Dialector = dialector

然后,又通過config.Dialector的Initialize函數對數據庫進行了初始化。如下:

err = config.Dialector.Initialize(db)

那么,Dialector是什么呢?Dialector是通過gorm.Open函數的第一個參數傳進來的。我們看具體的是什么。

三、Dialector參數

在gorm.Open函數中,第一個參數是Dialector類型的參數,這是一個接口類型。也就是說只要實現了該接口,就能作為一個Dialector。這也就是gorm能夠針對很多數據庫進行操作的原因。比如MySQL、ClickHouse等。Dialector接口類型定義如下:

// Dialector GORM database dialector
type 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
}

具體到mysql的數據庫,我們看到是通過gorm.io/driver/mysql?庫的Open函數來初始化的。我們看下mysql.Open函數的實現,如下:

func Open(dsn string) gorm.Dialector {
 dsnConf, _ := mysql.ParseDSN(dsn)
 return &Dialector{Config: &Config{DSN: dsn, DSNConfig: dsnConf}}
}

該函數接收一個dsn的字符串,也就是第一節中我們提供的和數據庫相關的賬號密碼等連接數據的信息。然后,返回的是mysql驅動包中的Dialector對象。該對象包含了相關的配置。

然后,是在gorm.Open函數中,調用了Dialector的Initialize?函數。我們看下該函數中和數據庫連接相關的邏輯。

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
  }
 }
    // 省略其他代碼
}

大家看到,在第13行的地方,是通過sql.Open函數來進行具體的和數據庫進行連接的。然后返回的對象是sql.DB類型,大家注意,這里的sql.DB類型是go標準庫中的DB,而非gorm庫中的DB。返回的sql.DB對象賦值給了gorm中DB對象中的ConnPool。

同時,在gorm.Open函數中,還將db.ConnPool對象賦值給了db.Statement.ConnPool對象。到這里是不是gorm.DB結構體中的字段就和數據庫的具體連接關聯起來。

接下來,我們再看看sql.Open函數是如何和數據庫建立連接的。

四、sql.Open函數

先看sql.Open函數的源代碼:

func Open(driverName, dataSourceName string) (*DB, error) {
 driversMu.RLock()
 driveri, ok := drivers[driverName]
 driversMu.RUnlock()
 if !ok {
  return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName)
 }

 if driverCtx, ok := driveri.(driver.DriverContext); ok {
  connector, err := driverCtx.OpenConnector(dataSourceName)
  if err != nil {
   return nil, err
  }
  return OpenDB(connector), nil
 }

 return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil
}

我們先簡單分析下上述代碼。在第3行處,從drivers中獲取對應的驅動名稱的具體驅動對象。這里的driverName是mysql。然后從第9行到第14行是執行具體驅動程序的連接函數。

首先,我們先看從drivers中根據驅動名稱mysql獲取驅動對象的邏輯。drivers是標準庫sql中的一個map類型,如下:

drivers   = make(map[string]driver.Driver)

該變量是通過sql包中的Register函數進行注冊的:

func Register(name string, driver driver.Driver) {
 driversMu.Lock()
 defer driversMu.Unlock()
 if driver == nil {
  panic("sql: Register driver is nil")
 }
 if _, dup := drivers[name]; dup {
  panic("sql: Register called twice for driver " + name)
 }
 drivers[name] = driver
}

該函數又是在哪里進行調用的呢?我們再回調gorm.Open函數中,第一個參數調用的是mysql.Open函數。也就是說引入了庫gorm.io/driver/mysql?,在該庫中,我們看到又引入了github.com/go-sql-driver/mysql?庫。該庫中有一個init方法,如下:

func init() {
 sql.Register("mysql", &MySQLDriver{})
}

原來,這里調用了標準庫sql中的Register函數,將“mysql”和對應的驅動對象MySQLDriver進行了注冊關聯。

我們再返回來看sql.Open函數的具體實現。那這里就繼續調用MySQLDriver的OpenConnector方法。我們看下該方法的實現如下:

// OpenConnector implements driver.DriverContext.
func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) {
 cfg, err := ParseDSN(dsn)
 if err != nil {
  return nil, err
 }
 return &connector{
  cfg: cfg,
 }, nil
}

該函數首先通過ParseDSN解析dsn字符串中的用戶名,地址,密碼等配置選項。然后返回一個connector對象。該connector對象就是在sql.Open函數中執行的OpenDB(connector)函數中的參數。

我們繼續看sql.OpenDB函數的實現,如下:

func OpenDB(c driver.Connector) *DB {
 ctx, cancel := context.WithCancel(context.Background())
 db := &DB{
  connector:    c,
  openerCh:     make(chan struct{}, connectionRequestQueueSize),
  lastPut:      make(map[*driverConn]string),
  connRequests: make(map[uint64]chan connRequest),
  stop:         cancel,
 }

 go db.connectionOpener(ctx)

 return db
}

這里首先構建了一個sql.DB對象,同時執行了一個協程進行數據庫的連接:

go db.connectionOpener(ctx)

接著看db.connectionOpener函數的實現,如下:

// Runs in a separate goroutine, opens new connections when requested.
func (db *DB) connectionOpener(ctx context.Context) {
 for {
  select {
  case <-ctx.Done():
   return
  case <-db.openerCh:
   db.openNewConnection(ctx)
  }
 }
}

這里,有一個db.openNewConnection函數,根據名字可知是打開新的連接。其實現如下:

// Open one new connection
func (db *DB) openNewConnection(ctx context.Context) {

 ci, err := db.connector.Connect(ctx)
    // ...省略代碼

 dc := &driverConn{
  db:         db,
  createdAt:  nowFunc(),
  returnedAt: nowFunc(),
  ci:         ci,
 }
 if db.putConnDBLocked(dc, err) {
  db.addDepLocked(dc, dc)
 } else {
  db.numOpen--
  ci.Close()
 }
}

這里我們看到有一個db.connector.Connect函數,connector就是github.com/go-sql-driver/mysql?庫中的connector對象。我們回到該庫,查看其Connect函數的實現:

// Connect implements driver.Connector interface.
// Connect returns a connection to the database.
func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 var err error

 // New mysqlConn
 mc := &mysqlConn{
  maxAllowedPacket: maxPacketSize,
  maxWriteSize:     maxPacketSize - 1,
  closech:          make(chan struct{}),
  cfg:              c.cfg,
 }
 mc.parseTime = mc.cfg.ParseTime

 // Connect to Server
 dialsLock.RLock()
 dial, ok := dials[mc.cfg.Net]
 dialsLock.RUnlock()
 if ok {
     //...省略代碼
 } else {
  nd := net.Dialer{Timeout: mc.cfg.Timeout}
  mc.netConn, err = nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr)
 }

 // Enable TCP Keepalives on TCP connections
 if tc, ok := mc.netConn.(*net.TCPConn); ok {
  if err := tc.SetKeepAlive(true); err != nil {
            //...省略代碼
  }
 }

 mc.buf = newBuffer(mc.netConn)
 //...

 // Reading Handshake Initialization Packet
 authData, plugin, err := mc.readHandshakePacket()
 if err != nil {
  mc.cleanup()
  return nil, err
 }


 // Send Client Authentication Packet
 authResp, err := mc.auth(authData, plugin)

 if err = mc.writeHandshakeResponsePacket(authResp, plugin); err != nil {
  mc.cleanup()
  return nil, err
 }

 // Handle response to auth packet, switch methods if possible
 if err = mc.handleAuthResult(authData, plugin); err != nil {
  mc.cleanup()
  return nil, err
 }

 return mc, nil
}

這里我們主要看第22到23行,這里進行了實際的撥號操作,也就是和數據庫真正的建立了連接。再看第27行,斷言是一個TCP連接。第37行,進行了握手處理;第45行,進行了認證處理。最終返回了一個mysqlConn對象。該mysqlConn結構體中包含字段如下:

type mysqlConn struct {
 buf              buffer
 netConn          net.Conn
 rawConn          net.Conn // underlying connection when netConn is TLS connection.
    // ...
}

其中,netConn就是和數據庫建立的TCP的連接。

五、從mysql到gorm.DB

我們再總結下上述和mysql相關的各個對象之間的關聯關系。從mysql開始逆向推導。如下:圖片

也就是說,我們在使用gorm進行數據庫操作的時候,最終都是從gorm.Statement.ConnPool中獲取的數據庫連接來具體執行sql語句的。

好了,今天gorm是如何和mysql建立連接的過程就跟大家分享到這里。

責任編輯:武曉燕 來源: Go學堂
相關推薦

2023-08-24 08:47:38

2023-10-08 08:11:54

2024-12-02 11:39:30

2010-06-01 15:25:27

JavaCLASSPATH

2016-12-08 15:36:59

HashMap數據結構hash函數

2020-07-21 08:26:08

SpringSecurity過濾器

2012-11-22 10:11:16

LispLisp教程

2019-09-16 08:32:59

遞歸算法編程

2024-10-21 08:08:56

2020-09-23 10:00:26

Redis數據庫命令

2019-06-25 10:32:19

UDP編程通信

2017-01-10 08:48:21

2024-02-21 21:14:20

編程語言開發Golang

2025-05-06 00:43:00

MySQL日志文件MIXED 3

2017-08-15 13:05:58

Serverless架構開發運維

2025-06-05 05:51:33

2023-10-19 11:12:15

Netty代碼

2021-02-17 11:25:33

前端JavaScriptthis

2009-09-25 09:14:35

Hibernate日志

2013-09-22 14:57:19

AtWood
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久69精品久久久久久久电影好 | 成人免费视频 | 美女久久视频 | 久久男人 | 精品在线免费观看视频 | 黄a在线观看 | 久久av网| 亚洲成人国产综合 | 在线观看a视频 | 国产www.| 成人在线视频网址 | 日本三级线观看 视频 | 中文字幕视频在线观看 | 中文字幕一区二区三区在线乱码 | 羞羞视频网站免费观看 | 国产精品一区二区在线 | 日韩电影在线一区 | 久久大| 欧美激情综合五月色丁香小说 | 国产h视频 | 久久精品久久久久久 | 国产精品v| 国产高清美女一级a毛片久久w | 久久在视频 | 在线免费亚洲视频 | 欧产日产国产精品99 | 国产一区二区电影 | 欧美精品乱码99久久影院 | 色婷婷综合久久久久中文一区二区 | 久久成人一区 | 欧美日韩一卡 | 久久久精品网 | 久久激情视频 | 中文一区 | 免费超碰 | 亚洲精品www | 五月婷婷色 | 成人精品一区二区 | 自拍 亚洲 欧美 老师 丝袜 | 精品欧美一区二区三区久久久小说 | 黄色一级大片在线免费看产 |