最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入理解gorm如何和數(shù)據(jù)庫建立連接

 更新時間:2023年11月06日 11:22:44   作者:Go學堂  
這篇文章主要為大家詳細介紹了gorm如何和數(shù)據(jù)庫建立連接,文中的示例代碼講解詳細,對我們深入了解GO語言有一定的幫助,需要的小伙伴可以參考下

一、gorm.Open

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

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{})
}

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

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

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

二、gorm.Open函數(shù)

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

config.Dialector = dialector

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

err = config.Dialector.Initialize(db)

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

三、Dialector參數(shù)

在gorm.Open函數(shù)中,第一個參數(shù)是Dialector類型的參數(shù),這是一個接口類型。也就是說只要實現(xiàn)了該接口,就能作為一個Dialector。這也就是gorm能夠針對很多數(shù)據(jù)庫進行操作的原因。比如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的數(shù)據(jù)庫,我們看到是通過gorm.io/driver/mysql庫的Open函數(shù)來初始化的。我們看下mysql.Open函數(shù)的實現(xiàn),如下:

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

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

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

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函數(shù)來進行具體的和數(shù)據(jù)庫進行連接的。然后返回的對象是sql.DB類型,大家注意,這里的sql.DB類型是go標準庫中的DB,而非gorm庫中的DB。返回的sql.DB對象賦值給了gorm中DB對象中的ConnPool。

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

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

四、sql.Open函數(shù)

先看sql.Open函數(shù)的源代碼:

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行是執(zhí)行具體驅動程序的連接函數(shù)。

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

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

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

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
}

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

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

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

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

// 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
}

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

我們繼續(xù)看sql.OpenDB函數(shù)的實現(xiàn),如下:

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對象,同時執(zhí)行了一個協(xié)程進行數(shù)據(jù)庫的連接:

go db.connectionOpener(ctx)

接著看db.connectionOpener函數(shù)的實現(xiàn),如下:

// 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函數(shù),根據(jù)名字可知是打開新的連接。其實現(xiàn)如下:

// 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函數(shù),connector就是github.com/go-sql-driver/mysql庫中的connector對象。我們回到該庫,查看其Connect函數(shù)的實現(xiàn):

// 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行,這里進行了實際的撥號操作,也就是和數(shù)據(jù)庫真正的建立了連接。再看第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就是和數(shù)據(jù)庫建立的TCP的連接。

五、從mysql到gorm.DB

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

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

到此這篇關于深入理解gorm如何和數(shù)據(jù)庫建立連接的文章就介紹到這了,更多相關gorm連接數(shù)據(jù)庫內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • golang程序打包成腳本部署到Linux系統(tǒng)方式

    golang程序打包成腳本部署到Linux系統(tǒng)方式

    Golang程序通過本地編譯(設置GOOS為linux生成無后綴二進制文件),上傳至Linux服務器后賦權執(zhí)行,使用nohup命令實現(xiàn)后臺運行,完成自動化部署
    2025-07-07
  • Go語言對字符串進行MD5加密的方法

    Go語言對字符串進行MD5加密的方法

    這篇文章主要介紹了Go語言對字符串進行MD5加密的方法,實例分析了Go語言對字符串進行md5加密的技巧,需要的朋友可以參考下
    2015-03-03
  • 使用go操作redis的有序集合(zset)

    使用go操作redis的有序集合(zset)

    這篇文章主要介紹了使用go操作redis的有序集合(zset),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go語言中的Array、Slice、Map和Set使用詳解

    Go語言中的Array、Slice、Map和Set使用詳解

    這篇文章主要介紹了Go語言中的Array、Slice、Map和Set使用詳解,本文給出了它們的創(chuàng)建、使用、多維等代碼實例,需要的朋友可以參考下
    2014-10-10
  • go Gin安裝及初始化操作示例

    go Gin安裝及初始化操作示例

    這篇文章主要介紹了gin安裝及初始化,修改啟動端口,get/post?請求參數(shù),模型綁定shouldbind,自定義驗證器/表單驗證,等操作步驟,有需要的朋友可以借鑒參考下
    2022-04-04
  • 在Go中使用jwt的教程詳解

    在Go中使用jwt的教程詳解

    JWT (JSON Web Tokens) 是一種基于 JSON 格式的輕量級身份驗證和授權方案,用于在各方之間以JSON方式安全地傳輸信息,本文給大家詳細介紹了在Go中使用jwt的教程,文中通過代碼示例講解的非常詳細,需要的朋友可以參考下
    2024-06-06
  • 深入了解Golang官方container/list原理

    深入了解Golang官方container/list原理

    在?Golang?的標準庫?container?中,包含了幾種常見的數(shù)據(jù)結構的實現(xiàn),其實是非常好的學習材料,本文主要為大家介紹了container/list的原理與使用,感興趣的可以了解一下
    2023-08-08
  • 詳解如何保留Go程序崩潰現(xiàn)場

    詳解如何保留Go程序崩潰現(xiàn)場

    這篇文章主要為大家介紹了如何保留Go程序崩潰現(xiàn)場示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • Golang時間比較的幾種方法小結

    Golang時間比較的幾種方法小結

    本文主要介紹了Golang時間比較的幾種方法小結,包括Before、 After、 Equal, 但是都需要轉成time類型來比較,下面就一起來了解一下,感興趣的可以了解游戲
    2025-06-06
  • Golang通脈之map詳情

    Golang通脈之map詳情

    這篇文章主要介紹了Golang通脈之map,Go語言中提供的映射關系容器為map,其內部使用散列表(hash)實現(xiàn),map 是一種無序的鍵值對的集合。map 最重要的一點是通過 key 來快速檢索數(shù)據(jù),key 類似于索引,指向數(shù)據(jù)的值 map 是一種集合,所以可以像迭代數(shù)組和切片那樣迭代它
    2021-10-10

最新評論

尚志市| 尉氏县| 中山市| 安新县| 天门市| 区。| 崇阳县| 金沙县| 河池市| 诸暨市| 宣恩县| 怀集县| 永济市| 高雄市| 响水县| 寿阳县| 甘德县| 包头市| 右玉县| 乐东| 大同县| 濉溪县| 屏边| 宁晋县| 洛川县| 甘孜| 吉木乃县| 营口市| 册亨县| 信宜市| 巴塘县| 芜湖市| 永福县| 阿克苏市| 连南| 天等县| 福海县| 朝阳区| 怀安县| 镇巴县| 修武县|