亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go語言中ORM框架GORM使用介紹

 更新時間:2023年06月07日 11:42:44   作者:江湖十年  
GORM是Go語言中最受歡迎的ORM庫之一,它提供了強大的功能和簡潔的?API,讓數(shù)據(jù)庫操作變得更加簡單和易維護,本文將詳細(xì)介紹GORM的常見用法,包括數(shù)據(jù)庫連接、模型定義、CRUD、事務(wù)管理等方面,幫助大家快速上手使用GORM進行Web后端開發(fā)

安裝

通過如下命令安裝 GORM:

$ go get -u gorm.io/gorm

你也許見過使用 go get -u github.com/jinzhu/gorm 命令來安裝 GORM,這個是老版本 v1,現(xiàn)已過時,不建議使用。新版本 v2 已經(jīng)遷移至 github.com/go-gorm/gorm 倉庫下。

快速開始

如下示例代碼帶你快速上手 GORM 的使用:

package main
import (
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)
// Product 定義結(jié)構(gòu)體用來映射數(shù)據(jù)庫表
type Product struct {
	gorm.Model
	Code  string
	Price uint
}
func main() {
	// 建立數(shù)據(jù)庫連接
	db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}
	// 遷移表結(jié)構(gòu)
	db.AutoMigrate(&Product{})
	// 增加數(shù)據(jù)
	db.Create(&Product{Code: "D42", Price: 100})
	// 查找數(shù)據(jù)
	var product Product
	db.First(&product, 1)                 // find product with integer primary key
	db.First(&product, "code = ?", "D42") // find product with code D42
	// 更新數(shù)據(jù) - update product's price to 200
	db.Model(&product).Update("Price", 200)
	// 更新數(shù)據(jù) - update multiple fields
	db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // non-zero fields
	db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
	// 刪除數(shù)據(jù) - delete product
	db.Delete(&product, 1)
}

提示:這里使用了 SQLite 數(shù)據(jù)庫驅(qū)動,需要通過 go get -u gorm.io/driver/sqlite 命令安裝。

將以上代碼保存在 main.go 中并執(zhí)行。

$ go run main.go

執(zhí)行完成后,我們將在當(dāng)前目錄下得到 test.db SQLite 數(shù)據(jù)庫文件。

① 進入 SQLite 命令行。

② 查看已存在的數(shù)據(jù)庫表。

③ 設(shè)置稍后查詢表數(shù)據(jù)時的輸出模式為按列左對齊。

④ 查詢表中存在的數(shù)據(jù)。

有過使用 ORM 框架經(jīng)驗的同學(xué),以上代碼即使我不進行講解也能看懂個大概。

這段示例代碼基本能夠概括 GORM 框架使用套路:

  • 定義結(jié)構(gòu)體映射表結(jié)構(gòu):Product 結(jié)構(gòu)體在 GORM 中稱作「模型」,一個模型對應(yīng)一張數(shù)據(jù)庫表,一個結(jié)構(gòu)體實例對象對應(yīng)一條數(shù)據(jù)庫表記錄。

  • 連接數(shù)據(jù)庫:GORM 使用 gorm.Open 方法與數(shù)據(jù)庫建立連接,連接建立好后,才能對數(shù)據(jù)庫進行 CRUD 操作。

  • 自動遷移表結(jié)構(gòu):調(diào)用 db.AutoMigrate 方法能夠自動完成在數(shù)據(jù)庫中創(chuàng)建 Product 結(jié)構(gòu)體所映射的數(shù)據(jù)庫表,并且,當(dāng) Product 結(jié)構(gòu)體字段有變更,再次執(zhí)行遷移代碼,GORM 會自動對表結(jié)構(gòu)進行調(diào)整,非常方便。不過,我不推薦在生產(chǎn)環(huán)境項目中使用此功能。因為數(shù)據(jù)庫表操作都是高風(fēng)險操作,一定要經(jīng)過多人 Review 并審核通過,才能執(zhí)行操作。GORM 自動遷移功能雖然理論上不會出現(xiàn)問題,但線上操作謹(jǐn)慎為妙,個人認(rèn)為只有在小項目或數(shù)據(jù)不那么重要的項目中使用比較合適。

  • CRUD 操作:遷移好數(shù)據(jù)庫后,就有了數(shù)據(jù)庫表,可以進行 CRUD 操作了。

有些同學(xué)可能有個疑問,以上示例代碼中并沒有類似 defer db.Close() 主動關(guān)閉連接的操作,那么何時關(guān)閉數(shù)據(jù)庫連接?

其實 GORM 維護了一個數(shù)據(jù)庫連接池,初始化 db 后所有的連接都由底層庫來管理,無需程序員手動干預(yù),GORM 會在合適的時機自動關(guān)閉連接。GORM 框架作者 jinzhu 也有在源碼倉庫 Issue 中回復(fù)過網(wǎng)友的提問,感興趣的同學(xué)可以點擊進入查看。

接下來我將對 GORM 的使用進行詳細(xì)講解。

聲明模型

GORM 使用模型(Model)來映射一張數(shù)據(jù)庫表,模型是標(biāo)準(zhǔn)的 Go struct,由 Go 的基本數(shù)據(jù)類型、實現(xiàn)了 ScannerValuer 接口的自定義類型及其指針或別名組成。

例如:

type User struct {
	ID           uint
	Name         string
	Email        *string
	Age          uint8
	Birthday     *time.Time
	MemberNumber sql.NullString
	ActivatedAt  sql.NullTime
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

我們可以使用 gorm 字段標(biāo)簽來控制數(shù)據(jù)庫表字段的類型、列大小、默認(rèn)值等屬性,比如使用 column 字段標(biāo)簽來映射數(shù)據(jù)庫中字段名稱。

type User struct {
	gorm.Model
	Name         string         `gorm:"column:name"`
	Email        *string        `gorm:"column:email"`
	Age          uint8          `gorm:"column:age"`
	Birthday     *time.Time     `gorm:"column:birthday"`
	MemberNumber sql.NullString `gorm:"column:member_number"`
	ActivatedAt  sql.NullTime   `gorm:"column:activated_at"`
}
func (u *User) TableName() string {
	return "user"
}

在不指定 column 字段標(biāo)簽情況下,GORM 默認(rèn)使用字段名的 snake_case 作為列名。

GORM 默認(rèn)使用結(jié)構(gòu)體名的 snake_cases 作為表名,為結(jié)構(gòu)體實現(xiàn) TableName 方法可以自定義表名。

我更喜歡「顯式勝于隱式」的做法,所以數(shù)據(jù)庫名和表名都會顯示寫出來。

因為我們不使用自動遷移的功能,所以其他字段標(biāo)簽都用不到,就不在此一一介紹了,感興趣的同學(xué)可以查看官方文檔進行學(xué)習(xí)。

User 結(jié)構(gòu)體中有一個嵌套的結(jié)構(gòu)體 gorm.Model,它是 GORM 默認(rèn)提供的一個模型 struct,用來簡化用戶模型定義。

GORM 傾向于約定優(yōu)于配置,默認(rèn)情況下,使用 ID 作為主鍵,使用 CreatedAtUpdatedAt、DeletedAt 字段追蹤記錄的創(chuàng)建、更新、刪除時間。而這幾個字段就定義在 gorm.Model 中:

type Model struct {
	ID        uint `gorm:"primarykey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt DeletedAt `gorm:"index"`
}

由于我們不使用自動遷移功能,所以需要手動編寫 SQL 語句來創(chuàng)建 user 數(shù)據(jù)庫表結(jié)構(gòu):

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT '' COMMENT '用戶名',
  `email` varchar(255) NOT NULL DEFAULT '' COMMENT '郵箱',
  `age` tinyint(4) NOT NULL DEFAULT '0' COMMENT '年齡',
  `birthday` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '生日',
  `member_number` varchar(50) COMMENT '成員編號',
  `activated_at` datetime COMMENT '激活時間',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `deleted_at` datetime,
  PRIMARY KEY (`id`),
  UNIQUE KEY `u_email` (`email`),
  INDEX `idx_deleted_at`(`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶表';

數(shù)據(jù)庫中字段類型要跟 Go 中模型的字段類型相對應(yīng),不兼容的類型可能導(dǎo)致錯誤。

連接數(shù)據(jù)庫

GORM 官方支持的數(shù)據(jù)庫類型有:MySQL、PostgreSQL、SQLite、SQL Server 和 TiDB。

這里使用最常見的 MySQL 作為示例,來講解 GORM 如何連接到數(shù)據(jù)庫。

在前文快速開始的示例代碼中,我們使用 SQLite 數(shù)據(jù)庫時,安裝了 sqlite 驅(qū)動程序。要連接 MySQL 則需要使用 mysql 驅(qū)動。

在 GORM 中定義了 gorm.Dialector 接口來規(guī)范數(shù)據(jù)庫連接操作,實現(xiàn)了此接口的程序我們將其稱為「驅(qū)動」。針對每種數(shù)據(jù)庫,都有對應(yīng)的驅(qū)動,驅(qū)動是獨立于 GORM 庫的,需要單獨引入。

連接 MySQL 數(shù)據(jù)庫的代碼如下:

package main
import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)
func ConnectMySQL(host, port, user, pass, dbname string) (*gorm.DB, error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		user, pass, host, port, dbname)
	return gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

可以發(fā)現(xiàn),這段代碼與連接 SQLite 數(shù)據(jù)庫的代碼如出一轍,這就是面向接口編程的好處。

首先,mysql.Open 接收一個字符串 dsn,DSN 全稱 Data Source Name,翻譯過來叫數(shù)據(jù)庫源名稱。DSN 定義了一個數(shù)據(jù)庫的連接信息,包含用戶名、密碼、數(shù)據(jù)庫 IP、數(shù)據(jù)庫端口、數(shù)據(jù)庫字符集、數(shù)據(jù)庫時區(qū)等信息。DSN 遵循特定格式:

username:password@protocol(address)/dbname?param=value

通過 DSN 所包含的信息,mysql 驅(qū)動就能夠知道以什么方式連接到 MySQL 數(shù)據(jù)庫了。

mysql.Open 返回的正是一個 gorm.Dialector 對象,將其傳遞給 gorm.Open 方法后,我們將得到 *gorm.DB 對象,這個對象可以用來操作數(shù)據(jù)庫。

GORM 使用 database/sql 來維護數(shù)據(jù)庫連接池,對于連接池我們可以設(shè)置如下幾個參數(shù):

func SetConnect(db *gorm.DB) error {
	sqlDB, err := db.DB()
	if err != nil {
		return err
	}
	sqlDB.SetMaxOpenConns(100)                 // 設(shè)置數(shù)據(jù)庫的最大打開連接數(shù)
	sqlDB.SetMaxIdleConns(100)                 // 設(shè)置最大空閑連接數(shù)
	sqlDB.SetConnMaxLifetime(10 * time.Second) // 設(shè)置空閑連接最大存活時間
	return nil
}

現(xiàn)在,數(shù)據(jù)庫連接已經(jīng)建立,我們可以對數(shù)據(jù)庫進行操作了。

創(chuàng)建

可以使用 Create 方法創(chuàng)建一條數(shù)據(jù)庫記錄:

now := time.Now()
email := "u1@jianghushinian.com"
user := User{Name: "user1", Email: &email, Age: 18, Birthday: &now}
// INSERT INTO `user` (`created_at`,`updated_at`,`deleted_at`,`name`,`email`,`age`,`birthday`,`member_number`,`activated_at`) VALUES ('2023-05-22 22:14:47.814','2023-05-22 22:14:47.814',NULL,'user1','u1@jianghushinian.com',18,'2023-05-22 22:14:47.812',NULL,NULL)
result := db.Create(&user) // 通過數(shù)據(jù)的指針來創(chuàng)建
fmt.Printf("user: %+v\n", user) // user.ID 自動填充
fmt.Printf("affected rows: %d\n", result.RowsAffected)
fmt.Printf("error: %v\n", result.Error)

要創(chuàng)建記錄,我們需要先實例化 User 對象,然后將其指針傳遞給 db.Create 方法。

db.Create 方法執(zhí)行完成后,依然返回一個 *gorm.DB 對象。

user.ID 會被自動填充為創(chuàng)建數(shù)據(jù)庫記錄后返回的真實值。

result.RowsAffected 可以拿到此次操作影響行數(shù)。

result.Error 可以知道執(zhí)行 SQL 是否出錯。

在這里,我將 db.Create(&user) 這句 ORM 代碼所生成的原生 SQL 語句放在了注釋中,方便你對比學(xué)習(xí)。并且,之后的示例中我也會這樣做。

Create 方法不僅支持創(chuàng)建單條記錄,它同樣支持批量操作,一次創(chuàng)建多條記錄:

now = time.Now()
email2 := "u2@jianghushinian.com"
email3 := "u3@jianghushinian.com"
users := []User{
	{Name: "user2", Email: &email2, Age: 19, Birthday: &now},
	{Name: "user3", Email: &email3, Age: 20, Birthday: &now},
}
// INSERT INTO `user` (`created_at`,`updated_at`,`deleted_at`,`name`,`email`,`age`,`birthday`,`member_number`,`activated_at`) VALUES ('2023-05-22 22:14:47.834','2023-05-22 22:14:47.834',NULL,'user2','u2@jianghushinian.com',19,'2023-05-22 22:14:47.833',NULL,NULL),('2023-05-22 22:14:47.834','2023-05-22 22:14:47.834',NULL,'user3','u3@jianghushinian.com',20,'2023-05-22 22:14:47.833',NULL,NULL)
result = db.Create(&users)

代碼主要邏輯不變,只需要將單個的 User 實例換成 User 切片即可。GORM 會使用一條 SQL 語句完成批量創(chuàng)建記錄。

查詢

查詢記錄是我們在日常開發(fā)中使用最多的場景了,GORM 提供了多種方法來支持 SQL 查詢操作。

使用 First 方法可以查詢第一條記錄:

var user User
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL ORDER BY `user`.`id` LIMIT 1
result := db.First(&user)

First 方法接收一個模型指針,通過模型的 TableName 方法則可以拿到數(shù)據(jù)庫表名,然后使用 SELECT * 語句從數(shù)據(jù)庫中查詢記錄。

根據(jù)生成的 SQL 可以發(fā)現(xiàn) First 方法查詢數(shù)據(jù)默認(rèn)根據(jù)主鍵 ID 升序排序,并且只會過濾刪除時間為 NULL 的數(shù)據(jù),使用 LIMIT 關(guān)鍵字來限制數(shù)據(jù)條數(shù)。

使用 Last 方法可以查詢最后一條數(shù)據(jù),排序規(guī)則為主鍵 ID 降序:

var lastUser User
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL ORDER BY `user`.`id` DESC LIMIT 1
result = db.Last(&lastUser)

使用 Where 方法可以增加查詢條件:

var users []User
// SELECT * FROM `user` WHERE name != 'unknown' AND `user`.`deleted_at` IS NULL
result = db.Where("name != ?", "unknown").Find(&users)

這里不再查詢單條數(shù)據(jù),所以改用 Find 方法來查詢所有符合條件的記錄。

以上介紹的幾種查詢方法,都是通過 SELECT * 查詢數(shù)據(jù)庫表中的全部字段,我們可以使用 Select 方法指定需要查詢的字段:

var user2 User
// SELECT `name`,`age` FROM `user` WHERE `user`.`deleted_at` IS NULL ORDER BY `user`.`id` LIMIT 1
result = db.Select("name", "age").First(&user2)

使用 Order 方法可以自定義排序規(guī)則:

var users2 []User
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL ORDER BY id desc
result = db.Order("id desc").Find(&users2)

GORM 也提供了對 Limit & Offset 的支持:

var users3 []User
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL LIMIT 2 OFFSET 1
result = db.Limit(2).Offset(1).Find(&users3)

使用 -1 可以取消 Limit & Offset 的限制條件:

var users4 []User
var users5 []User
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL LIMIT 2 OFFSET 1; (users4)
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL; (users5)
result = db.Limit(2).Offset(1).Find(&users4).Limit(-1).Offset(-1).Find(&users5)

這段代碼會執(zhí)行兩條查詢語句,之所以能夠采用這種「鏈?zhǔn)秸{(diào)用」的方式執(zhí)行多條 SQL,是因為每個方法返回的都是 *gorm.DB 對象,這也是一種編程技巧。

使用 Count 方法可以統(tǒng)計記錄條數(shù):

var count int64
// SELECT count(*) FROM `user` WHERE `user`.`deleted_at` IS NULL
result = db.Model(&User{}).Count(&count)

有時候遇到比較復(fù)雜的業(yè)務(wù),我們可能需要使用 SQL 子查詢,子查詢可以嵌套在另一個查詢中,GORM 允許將 *gorm.DB 對象作為參數(shù)時生成子查詢:

var avgages []float64
// SELECT AVG(age) as avgage FROM `user` WHERE `user`.`deleted_at` IS NULL GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `user` WHERE name LIKE 'user%')
subQuery := db.Select("AVG(age)").Where("name LIKE ?", "user%").Table("user")
result = db.Model(&User{}).Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&avgages)

Having 方法簽名如下:

func (db *DB) Having(query interface{}, args ...interface{}) (tx *DB) 

第二個參數(shù)是一個范型 interface{},所以不僅可以接收字符串,GORM 在判斷其類型為 *gorm.DB 時,就會構(gòu)造一個子查詢。

更新

為了講解更新操作,我們需要先查詢一條記錄,之后的更新操作都是基于這條被查詢出來的 User 對象:

var user User
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL ORDER BY `user`.`id` LIMIT 1
result := db.First(&user)

更新操作只要修改 User 對象的屬性,然后調(diào)用 db.Save(&user) 方法即可完成:

user.Name = "John"
user.Age = 20
// UPDATE `user` SET `created_at`='2023-05-22 22:14:47.814',`updated_at`='2023-05-22 22:24:34.201',`deleted_at`=NULL,`name`='John',`email`='u1@jianghushinian.com',`age`=20,`birthday`='2023-05-22 22:14:47.813',`member_number`=NULL,`activated_at`=NULL WHERE `user`.`deleted_at` IS NULL AND `id` = 1
result = db.Save(&user)

在更新操作時,User 對象要保證 ID 屬性存在值,不然就變成了創(chuàng)建操作。

Save 方法會保存所有的字段,即使字段是對應(yīng)類型的零值。

除了使用 Save 方法更新所有字段,我們還可以使用 Update 方法更新指定字段:

// UPDATE `user` SET `name`='Jianghushinian',`updated_at`='2023-05-22 22:24:34.215' WHERE `user`.`deleted_at` IS NULL AND `id` = 1
result = db.Model(&user).Update("name", "Jianghushinian")

Update 只能支持更新單個字段,要想更新多個字段,可以使用 Updates 方法:

// UPDATE `user` SET `updated_at`='2023-05-22 22:29:35.19',`name`='JiangHu' WHERE `user`.`deleted_at` IS NULL AND `id` = 1
result = db.Model(&user).Updates(User{Name: "JiangHu", Age: 0})

注意,Updates 方法與 Save 方法有一個很大的不同之處,它只會更新非零值字段。Age 字段為零值,所以不會被更新。

如果一定要更新零值字段,除了可以使用上面的 Save 方法,還可以將 User 結(jié)構(gòu)體換成 map[string]interface{} 類型的 map 對象:

// UPDATE `user` SET `age`=0,`name`='JiangHu',`updated_at`='2023-05-22 22:29:35.623' WHERE `user`.`deleted_at` IS NULL AND `id` = 1
result = db.Model(&user).Updates(map[string]interface{}{"name": "JiangHu", "age": 0})

此外,更新數(shù)據(jù)時,還可以使用 gorm.Expr 來實現(xiàn) SQL 表達式:

// UPDATE `user` SET `age`=age + 1,`updated_at`='2023-05-22 22:24:34.219' WHERE `user`.`deleted_at` IS NULL AND `id` = 1
result = db.Model(&user).Update("age", gorm.Expr("age + ?", 1))

gorm.Expr("age + ?", 1) 方法調(diào)用會被轉(zhuǎn)換成 age=age + 1 SQL 表達式。

刪除

可以使用 Delete 方法刪除數(shù)記錄:

var user User
// UPDATE `user` SET `deleted_at`='2023-05-22 22:46:45.086' WHERE name = 'JiangHu' AND `user`.`deleted_at` IS NULL
result := db.Where("name = ?", "JiangHu").Delete(&user)

對于刪除操作,GORM 默認(rèn)使用邏輯刪除策略,不會對記錄進行物理刪除。

所以 Delete 方法在對數(shù)據(jù)進行刪除時,實際上執(zhí)行的是 SQL UPDATE 操作,而非 DELETE 操作。

deleted_at 字段更新為當(dāng)前時間,表示當(dāng)前數(shù)據(jù)已刪除。這也是為什么前文在講解查詢和更新的時候,生成的 SQL 語句都自動附加了 deleted_at IS NULL Where 條件的原因。

這樣就實現(xiàn)了邏輯層面的刪除,數(shù)據(jù)在數(shù)據(jù)庫中仍然存在,但查詢和更新的時候會將其過濾掉。

記錄被刪除后,我們無法通過如下代碼直接查詢到被邏輯刪除的記錄:

// SELECT * FROM `user` WHERE name = 'JiangHu' AND `user`.`deleted_at` IS NULL ORDER BY `user`.`id` LIMIT 1
result = db.Where("name = ?", "JiangHu").First(&user)
if err := result.Error; err != nil {
	fmt.Println(err) // record not found
}

這將得到一個錯誤 record not found

不過,GORM 提供了 Unscoped 方法,可以繞過邏輯刪除:

// SELECT * FROM `user` WHERE name = 'JiangHu' ORDER BY `user`.`id` LIMIT 1
result = db.Unscoped().Where("name = ?", "JiangHu").First(&user)

以上代碼能夠查詢出被邏輯刪除的記錄,生成的 SQL 語句中沒有包含 deleted_at IS NULL Where 條件。

對于比較重要的數(shù)據(jù),建議使用邏輯刪除,這樣可以在需要的時候恢復(fù)數(shù)據(jù),也便于故障追蹤。

不過,如果明確想要物理刪除一條記錄,同理可以使用 Unscoped 方法:

// DELETE FROM `user` WHERE name = 'JiangHu' AND `user`.`id` = 1
result = db.Unscoped().Where("name = ?", "JiangHu").Delete(&user)

關(guān)聯(lián)

日常開發(fā)中,多數(shù)情況下不只是對單表進行操作,還要對存在關(guān)聯(lián)關(guān)系的多表進行操作。

這里以一個博客系統(tǒng)最常見的三張表「文章表、評論表、標(biāo)簽表」為例,對 GORM 如何操作關(guān)聯(lián)表進行講解。

這里涉及最常見的關(guān)聯(lián)關(guān)系:一對多和多對多。一篇文章可以有多條評論,所以文章和評論是一對多關(guān)系;一篇文章可以存在多個標(biāo)簽,每個標(biāo)簽也可以包含多篇文章,所以文章和標(biāo)簽是多對多關(guān)系。

模型定義如下:

type Post struct {
	gorm.Model
	Title    string     `gorm:"column:title"`
	Content  string     `gorm:"column:content"`
	Comments []*Comment `gorm:"foreignKey:PostID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;references:ID"`
	Tags     []*Tag     `gorm:"many2many:post_tags"`
}
func (p *Post) TableName() string {
	return "post"
}
type Comment struct {
	gorm.Model
	Content string `gorm:"column:content"`
	PostID  uint   `gorm:"column:post_id"`
	Post    *Post
}
func (c *Comment) TableName() string {
	return "comment"
}
type Tag struct {
	gorm.Model
	Name string  `gorm:"column:name"`
	Post []*Post `gorm:"many2many:post_tags"`
}
func (t *Tag) TableName() string {
	return "tag"
}

在模型定義中,Post 文章模型使用 CommentsTags 分別保存關(guān)聯(lián)的評論和標(biāo)簽,這兩個字段不會保存在數(shù)據(jù)庫表中。

Comments 字段標(biāo)簽使用 foreignKey 來指明 Comments 表中的外鍵,并使用 constraint 指明了約束條件,references 指明 Comments 表外鍵引用 Post 表的 ID 字段。

其實現(xiàn)在生產(chǎn)環(huán)境中都不再推薦使用外鍵,各個表之間不再有數(shù)據(jù)庫層面的外鍵約束,在做 CRUD 操作時全部通過代碼層面來進行業(yè)務(wù)約束。這里為了演示 GORM 的外鍵和級聯(lián)操作功能,所以定義了這些結(jié)構(gòu)體標(biāo)簽。

Tags 字段標(biāo)簽使用 many2many 來指明多對多關(guān)聯(lián)表名。

對于 Comment 模型,PostID 字段就是外鍵,用來保存 Post.ID。Post 字段同樣不會保存在數(shù)據(jù)庫中,這種做法在 ORM 框架中非常常見。

接下來,我將同樣對關(guān)聯(lián)表的 CRUD 操作進行一一講解。

創(chuàng)建

創(chuàng)建 Post 時會自動創(chuàng)建與之關(guān)聯(lián)的 CommentsTags

var post Post
post = Post{
	Title:   "post1",
	Content: "content1",
	Comments: []*Comment{
		{Content: "comment1", Post: &post},
		{Content: "comment2", Post: &post},
	},
	Tags: []*Tag{
		{Name: "tag1"},
		{Name: "tag2"},
	},
}
result := db.Create(&post)

這里定義了一個文章對象 post,并且包含兩條評論和兩個標(biāo)簽。

注意 Comment 的 Post 字段引用了 &post,并沒有指定 PostID 外鍵字段,GORM 能夠正確處理它。

以上代碼將生成并依次執(zhí)行如下 SQL 語句:

BEGIN TRANSACTION;
INSERT INTO `tag` (`created_at`,`updated_at`,`deleted_at`,`name`) VALUES ('2023-05-22 22:56:52.923','2023-05-22 22:56:52.923',NULL,'tag1'),('2023-05-22 22:56:52.923','2023-05-22 22:56:52.923',NULL,'tag2') ON DUPLICATE KEY UPDATE `id`=`id`
INSERT INTO `post` (`created_at`,`updated_at`,`deleted_at`,`title`,`content`) VALUES ('2023-05-22 22:56:52.898','2023-05-22 22:56:52.898',NULL,'post1','content1') ON DUPLICATE KEY UPDATE `id`=`id`
INSERT INTO `comment` (`created_at`,`updated_at`,`deleted_at`,`content`,`post_id`) VALUES ('2023-05-22 22:56:52.942','2023-05-22 22:56:52.942',NULL,'comment1',1),('2023-05-22 22:56:52.942','2023-05-22 22:56:52.942',NULL,'comment2',1) ON DUPLICATE KEY UPDATE `post_id`=VALUES(`post_id`)
INSERT INTO `post_tags` (`post_id`,`tag_id`) VALUES (1,1),(1,2) ON DUPLICATE KEY UPDATE `post_id`=`post_id`
COMMIT;

可以發(fā)現(xiàn),與文章形成一對多關(guān)系的評論以及與文章形成多對多關(guān)系的標(biāo)簽,都會被創(chuàng)建,并且 GORM 會維護其關(guān)聯(lián)關(guān)系,而且這些操作全部在一個事務(wù)下完成。

此外,前文介紹的 Save 方法不僅能夠更新記錄,實際上它還支持創(chuàng)建記錄,當(dāng) Post 對象不存在主鍵 ID 時,Save 方法將會創(chuàng)建一條新的記錄:

var post3 Post
post3 = Post{
	Title:   "post3",
	Content: "content3",
	Comments: []*Comment{
		{Content: "comment33", Post: &post3},
	},
	Tags: []*Tag{
		{Name: "tag3"},
	},
}
result = db.Save(&post3)

以上代碼生成的 SQL 如下:

BEGIN TRANSACTION;
INSERT INTO `tag` (`created_at`,`updated_at`,`deleted_at`,`name`) VALUES ('2023-05-22 23:17:53.189','2023-05-22 23:17:53.189',NULL,'tag3') ON DUPLICATE KEY UPDATE `id`=`id`
INSERT INTO `post` (`created_at`,`updated_at`,`deleted_at`,`title`,`content`) VALUES ('2023-05-22 23:17:53.189','2023-05-22 23:17:53.189',NULL,'post3','content3') ON DUPLICATE KEY UPDATE `id`=`id`
INSERT INTO `comment` (`created_at`,`updated_at`,`deleted_at`,`content`,`post_id`) VALUES ('2023-05-22 23:17:53.19','2023-05-22 23:17:53.19',NULL,'comment33',0) ON DUPLICATE KEY UPDATE `post_id`=VALUES(`post_id`)
INSERT INTO `post_tags` (`post_id`,`tag_id`) VALUES (0,0) ON DUPLICATE KEY UPDATE `post_id`=`post_id`
COMMIT;

查詢

可以使用如下方式,根據(jù) Post 的 ID 查詢與之關(guān)聯(lián)的 Comments

var (
	post     Post
	comments []*Comment
)
post.ID = 1
// SELECT * FROM `comment` WHERE `comment`.`post_id` = 1 AND `comment`.`deleted_at` IS NULL
err := db.Model(&post).Association("Comments").Find(&comments)

注意??:傳遞給 Association 方法的參數(shù)是 Comments,即在 Post 模型中定義的字段,而非評論的模型名 Comment。這點一定不要搞錯了,不然執(zhí)行 SQL 時會報錯。

Post 是源模型,主鍵 ID 不能為空。Association 方法指定關(guān)聯(lián)字段名,在 Post 模型中關(guān)聯(lián)的評論使用 Comments 表示。最后使用 Find 方法來查詢關(guān)聯(lián)的評論。

在查詢 Post 時,我們可以預(yù)加載與之關(guān)聯(lián)的 Comments

post2 := Post{}
result := db.Preload("Comments").Preload("Tags").First(&post2)
fmt.Println(post2)
for i, comment := range post2.Comments {
	fmt.Println(i, comment)
}
for i, tag := range post2.Tags {
	fmt.Println(i, tag)
}

我們可以像往常一樣使用 First 方法查詢一條 Post 記錄,同時搭配使用 Preload 方法來指定預(yù)加載的關(guān)聯(lián)字段名,這樣在查詢 Post 記錄時,會將關(guān)聯(lián)字段表的記錄全部查詢出來,并賦值給關(guān)聯(lián)字段。

以上代碼將執(zhí)行如下 SQL:

BEGIN TRANSACTION;
SELECT * FROM `post` WHERE `post`.`deleted_at` IS NULL ORDER BY `post`.`id` LIMIT 1
SELECT * FROM `comment` WHERE `comment`.`post_id` = 1 AND `comment`.`deleted_at` IS NULL
SELECT * FROM `post_tags` WHERE `post_tags`.`post_id` = 1
SELECT * FROM `tag` WHERE `tag`.`id` IN (1,2) AND `tag`.`deleted_at` IS NULL
COMMIT;

GORM 通過多條 SQL 語句查詢出所有關(guān)聯(lián)記錄,并且將關(guān)聯(lián) Comments 和 Tags 分別賦值給 Post 模型對應(yīng)字段。

當(dāng)遇到多表查詢時,我們通常還會使用 JOIN 來連接多張表:

type PostComment struct {
	Title   string
	Comment string
}
postComment := PostComment{}
post3 := Post{}
post3.ID = 3
// SELECT post.title, comment.Content AS comment FROM `post` LEFT JOIN comment ON comment.post_id = post.id WHERE `post`.`deleted_at` IS NULL AND `post`.`id` = 3
result := db.Model(&post3).Select("post.title, comment.Content AS comment").Joins("LEFT JOIN comment ON comment.post_id = post.id").Scan(&postComment)

使用 Select 方法來指定需要查詢的字段,使用 Joins 方法來實現(xiàn) JOIN 功能,最終使用 Scan 方法可以將查詢結(jié)果掃描到 postComment 對象中。

針對一對多關(guān)聯(lián)關(guān)系,Joins 方法同樣支持預(yù)加載:

var comments2 []*Comment
// SELECT `comment`.`id`,`comment`.`created_at`,`comment`.`updated_at`,`comment`.`deleted_at`,`comment`.`content`,`comment`.`post_id`,`Post`.`id` AS `Post__id`,`Post`.`created_at` AS `Post__created_at`,`Post`.`updated_at` AS `Post__updated_at`,`Post`.`deleted_at` AS `Post__deleted_at`,`Post`.`title` AS `Post__title`,`Post`.`content` AS `Post__content` FROM `comment` LEFT JOIN `post` `Post` ON `comment`.`post_id` = `Post`.`id` AND `Post`.`deleted_at` IS NULL WHERE `comment`.`deleted_at` IS NULL
result = db.Joins("Post").Find(&comments2)
for i, comment := range comments2 {
	fmt.Println(i, comment)
	fmt.Println(i, comment.Post)
}

JOIN 功能的預(yù)加載無需顯式使用 Preload 來指明,只需要在 Joins 方法中指明一對多關(guān)系中一這一端模型 Post 即可,使用 Find 查詢 Comment 記錄。

根據(jù)生成的 SQL 可以發(fā)現(xiàn)查詢主表為 comment,副表為 post。并且副表的字段都被重命名為 模型名__字段名 的格式,如 Post__title(題外話:如果你使用過 Python 的 Django ORM 框架,那么對這個雙下劃線命名字段的做法應(yīng)該有種似曾相識的感覺)。

更新

同講解單表更新時一樣,我們需要先查詢出一條記錄,用來演示更新操作:

var post Post
// SELECT * FROM `post` WHERE `post`.`deleted_at` IS NULL ORDER BY `post`.`id` LIMIT 1
result := db.First(&post)

可以使用如下方法替換 Post 關(guān)聯(lián)的 Comments

comment := Comment{
	Content: "comment3",
}
err := db.Model(&post).Association("Comments").Replace([]*Comment{&comment})

仍然使用 Association 方法指定 Post 關(guān)聯(lián)的 CommentsReplace 方法用來完成替換操作。

這里要注意,Replace 方法返回結(jié)果不再是 *gorm.DB 對象,而是直接返回 error

生成 SQL 如下:

BEGIN TRANSACTION;
INSERT INTO `comment` (`created_at`,`updated_at`,`deleted_at`,`content`,`post_id`) VALUES ('2023-05-23 09:07:42.852','2023-05-23 09:07:42.852',NULL,'comment3',1) ON DUPLICATE KEY UPDATE `post_id`=VALUES(`post_id`)
UPDATE `post` SET `updated_at`='2023-05-23 09:07:42.846' WHERE `post`.`deleted_at` IS NULL AND `id` = 1
UPDATE `comment` SET `post_id`=NULL WHERE `comment`.`id` <> 8 AND `comment`.`post_id` = 1 AND `comment`.`deleted_at` IS NULL
COMMIT;

刪除

使用 Delete 刪除文章表時,不會刪除關(guān)聯(lián)表的數(shù)據(jù):

var post Post
// UPDATE `post` SET `deleted_at`='2023-05-23 09:09:58.534' WHERE id = 1 AND `post`.`deleted_at` IS NULL
result := db.Where("id = ?", 1).Delete(&post)

對于存在關(guān)聯(lián)關(guān)系的記錄,刪除時默認(rèn)同樣采用 UPDATE 操作,且不影響關(guān)聯(lián)數(shù)據(jù)。

如果想要在刪除評論時,順便刪除與文章的關(guān)聯(lián)關(guān)系,可以使用 Association 方法:

// UPDATE `comment` SET `post_id`=NULL WHERE `comment`.`post_id` = 6 AND `comment`.`id` IN (NULL) AND `comment`.`deleted_at` IS NULL
err := db.Model(&post2).Association("Comments").Delete(post2.Comments)

事務(wù)

GORM 提供了對事務(wù)的支持,這在復(fù)雜的業(yè)務(wù)邏輯中是必要的。

要在事務(wù)中執(zhí)行一系列操作,可以使用 Transaction 方法實現(xiàn):

func TransactionPost(db *gorm.DB) error {
	return db.Transaction(func(tx *gorm.DB) error {
		post := Post{
			Title: "Hello World",
		}
		if err := tx.Create(&post).Error; err != nil {
			return err
		}
		comment := Comment{
			Content: "Hello World",
			PostID:  post.ID,
		}
		if err := tx.Create(&comment).Error; err != nil {
			return err
		}
		return nil
	})
}

Transaction 方法內(nèi)部的代碼,都將在一個事務(wù)中被處理。Transaction 方法接收一個函數(shù),其參數(shù)為 tx *gorm.DB,事務(wù)中所有數(shù)據(jù)庫的操作,都應(yīng)該使用這個 tx 而非 db。

在執(zhí)行事務(wù)的函數(shù)中,返回任何錯誤,整個事務(wù)都將被回滾,返回 nil 則事務(wù)被提交。

除了使用 Transaction 自動管理事務(wù),我們還可以手動管理事務(wù):

func TransactionPostWithManually(db *gorm.DB) error {
	tx := db.Begin()
	post := Post{
		Title: "Hello World Manually",
	}
	if err := tx.Create(&post).Error; err != nil {
		tx.Rollback()
		return err
	}
	comment := Comment{
		Content: "Hello World Manually",
		PostID:  post.ID,
	}
	if err := tx.Create(&comment).Error; err != nil {
		tx.Rollback()
		return err
	}
	return tx.Commit().Error
}

db.Begin() 用于開啟事務(wù),并返回 tx,稍后的事務(wù)操作都應(yīng)使用這個 tx 對象。如果在處理事務(wù)的過程中遇到錯誤,可以使用 tx.Rollback() 回滾事務(wù),如果沒有問題,最終可以使用 tx.Commit() 提交事務(wù)。

注意:手動事務(wù),事務(wù)一旦開始,你就應(yīng)該使用 tx 處理數(shù)據(jù)庫操作。

鉤子

GORM 還支持 Hook 功能,Hook 是在創(chuàng)建、查詢、更新、刪除等操作之前、之后調(diào)用的函數(shù),用來管理對象的生命周期。

鉤子方法的函數(shù)簽名為 func(*gorm.DB) error,比如以下鉤子函數(shù)在創(chuàng)建操作之前觸發(fā):

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
	u.UUID = uuid.New()
	if u.Name == "admin" {
		return errors.New("invalid name")
	}
	return nil
}

比如我們?yōu)?User 模型定義 BeforeCreate 鉤子,這樣在創(chuàng)建 User 對象前,GORM 會自動調(diào)用此函數(shù),完成為 User 對象創(chuàng)建 UUID 以及用戶名合法性驗證功能。

GORM 支持的鉤子函數(shù)以及執(zhí)行時機如下:

鉤子函數(shù)執(zhí)行時機
BeforeSave調(diào)用 Save 前
AfterSave調(diào)用 Save 后
BeforeCreate插入記錄前
AfterCreate插入記錄后
BeforeUpdate更新記錄前
AfterUpdate更新記錄后
BeforeDelete刪除記錄前
AfterDelete刪除記錄后
AfterFind查詢記錄后

原生 SQL

雖然我們使用 ORM 框架往往是為了將原生 SQL 的編寫轉(zhuǎn)為面向?qū)ο缶幊蹋贿^對原生 SQL 的支持是一款 ORM 框架必備的功能。

可以使用 Raw 方法執(zhí)行原生查詢 SQL,并將結(jié)果 Scan 到模型中:

var userRes UserResult
db.Raw(`SELECT id, name, age FROM user WHERE id = ?`, 3).Scan(&userRes)
fmt.Printf("affected rows: %d\n", db.RowsAffected)
fmt.Println(db.Error)
fmt.Println(userRes)

原生 SQL 同樣支持使用表達式:

var sumage int
db.Raw(`SELECT SUM(age) as sumage FROM user WHERE member_number ?`, gorm.Expr("IS NULL")).Scan(&sumage)

此外,我們還可以使用 Exec 執(zhí)行任意原生 SQL:

db.Exec("UPDATE user SET age = ? WHERE id IN ?", 18, []int64{1, 2})
// 使用表達式
db.Exec(`UPDATE user SET age = ? WHERE name = ?`, gorm.Expr("age * ? + ?", 1, 2), "Jianghu")
// 刪除表
db.Exec("DROP TABLE user")

使用 Exec 無法拿到執(zhí)行結(jié)果,可以用來對表進行操作,比如增加、刪除表等。

編寫 SQL 時支持使用 @name 語法命名參數(shù):

db.Exec("UPDATE user SET age = ? WHERE id IN ?", 18, []int64{1, 2})
// 使用表達式
db.Exec(`UPDATE user SET age = ? WHERE name = ?`, gorm.Expr("age * ? + ?", 1, 2), "Jianghu")
// 刪除表
db.Exec("DROP TABLE user")

使用 DryRun 模式可以直接拿到由 GORM 生成的原生 SQL,而不執(zhí)行,方便后續(xù)使用:

var post Post
db.Where("title LIKE @name OR content LiKE @name", sql.Named("name", "%Hello%")).Find(&post)
var user User
// SELECT * FROM user WHERE name1 = "Jianghu" OR name2 = "shinian" OR name3 = "Jianghu"
db.Raw("SELECT * FROM user WHERE name1 = @name OR name2 = @name2 OR name3 = @name",
   sql.Named("name", "Jianghu"), sql.Named("name2", "shinian")).Find(&user)

DryRun 模式可以翻譯為空跑,意思是不執(zhí)行真正的 SQL,這在調(diào)試時非常有用。

調(diào)試

GORM 常用功能我們已經(jīng)基本講解完成了,最后再來介紹下在日常開發(fā)中,遇到問題如何進行調(diào)試。

GORM 調(diào)試方法我總結(jié)了如下 5 點:

  • 全局開啟日志

還記得在連接數(shù)據(jù)庫時 gorm.Open 方法的第二個參數(shù)嗎,我們當(dāng)時傳遞了一個空配置 &gorm.Config{},這個可選的參數(shù)可以改變 GORM 的一些默認(rèn)功能配置,比如我們可以設(shè)置日志級別為 Info,這樣就能夠在控制臺打印所有執(zhí)行的 SQL 語句:

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
	Logger:logger.Default.LogMode(logger.Info),
})
  • 打印慢查詢 SQL

有時候某段 ORM 代碼執(zhí)行很慢,我們可以通過開啟慢查詢?nèi)罩荆瑏頇z測 SQL 中的慢查詢語句:

func ConnectMySQL(host, port, user, pass, dbname string) (*gorm.DB, error) {
	slowLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags),
		logger.Config{
			// 設(shè)定慢查詢時間閾值為 3ms(默認(rèn)值:200 * time.Millisecond)
			SlowThreshold: 3 * time.Millisecond,
			// 設(shè)置日志級別
			LogLevel: logger.Warn,
		},
	)
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		user, pass, host, port, dbname)
	return gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: slowLogger,
	})
}
  • 打印指定 SQL

使用 Debug 能夠打印當(dāng)前 ORM 語句執(zhí)行的 SQL:

db.Debug().First(&User{}) 
  • 全局開啟 DryRun 模型

在連接數(shù)據(jù)庫時,我們可以全局開啟「空跑」模式:

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ DryRun: true, }) 

開啟 DryRun 模型后,任何 SQL 語句都不會真正執(zhí)行,方便測試。

  • 局部開啟 DryRun 模型

在當(dāng)前 Session 中局部開啟「空跑」模型,可以在不執(zhí)行操作的情況下生成 SQL 及其參數(shù),用于準(zhǔn)備或測試生成的 SQL:

var user User
stmt := db.Session(&gorm.Session{DryRun: true}).First(&user, 1).Statement
fmt.Println(stmt.SQL.String()) // => SELECT * FROM `users` WHERE `id` = $1 ORDER BY `id`
fmt.Println(stmt.Vars)         // => []interface{}{1}

總結(jié)

本文對 Go 語言中最流行的 ORM 框架 GORM 進行了講解,介紹了如何編寫模型,如何連接數(shù)據(jù)庫,以及最常使用的 CRUD 操作。并且還對關(guān)聯(lián)表中的一對多、多對多兩種關(guān)聯(lián)關(guān)系操作進行了講解。我們還介紹了必不可少的功能「事務(wù)」,GORM 還提供了鉤子函數(shù)方便我們在 CRUD 操作前后插入一些自定義邏輯。最后對如何使用原生 SQL 以及如何調(diào)試也進行了介紹。

只要你原生 SQL 基礎(chǔ)扎實,ORM 框架學(xué)習(xí)起來并不會太費力,并且我們還有各種調(diào)試方式來打印 GORM 所生成的 SQL,方便排查問題。

以上就是Go語言中ORM框架GORM使用介紹的詳細(xì)內(nèi)容,更多關(guān)于Go ORM框架GORM的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言基于viper的conf庫進行配置文件解析

    Go語言基于viper的conf庫進行配置文件解析

    在現(xiàn)代軟件開發(fā)中,配置文件是不可或缺的一部分,如何高效地將這些格式解析到 Go 結(jié)構(gòu)體中,一直是開發(fā)者的痛點,下面我們來看看如何使用conf進行配置文件解析吧
    2025-03-03
  • Go與C語言的互操作實現(xiàn)

    Go與C語言的互操作實現(xiàn)

    在Go與C語言互操作方面,Go更是提供了強大的支持。尤其是在Go中使用C,你甚至可以直接在Go源文件中編寫C代碼,本文就詳細(xì)的介紹一下如何使用,感興趣的可以了解一下
    2021-12-12
  • 深入了解Go語言中context的用法

    深入了解Go語言中context的用法

    這篇文章主要為大家詳細(xì)介紹了Go語言中context用法的相關(guān)知識,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-07-07
  • 詳解如何在Go語言中循環(huán)數(shù)據(jù)結(jié)構(gòu)

    詳解如何在Go語言中循環(huán)數(shù)據(jù)結(jié)構(gòu)

    這篇文章主要為大家詳細(xì)介紹了如何在Go語言中循環(huán)數(shù)據(jù)結(jié)構(gòu)(循環(huán)字符串、循環(huán)map結(jié)構(gòu)和循環(huán)Struct),文中的示例代碼代碼講解詳細(xì),需要的可以參考一下
    2022-10-10
  • Go Java算法最大單詞長度乘積示例詳解

    Go Java算法最大單詞長度乘積示例詳解

    這篇文章主要為大家介紹了Go Java算法最大單詞長度乘積示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • golang規(guī)則引擎gengine用法案例

    golang規(guī)則引擎gengine用法案例

    這篇文章主要為大家介紹了golang?規(guī)則引擎gengine用法案例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • go語言中的協(xié)程詳解

    go語言中的協(xié)程詳解

    本文詳細(xì)講解了go語言中的協(xié)程,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-07-07
  • Go語言操作redis用法實例

    Go語言操作redis用法實例

    這篇文章主要介紹了Go語言操作redis用法,實例分析了Go語言操作redis的技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02
  • golang在GRPC中設(shè)置client的超時時間

    golang在GRPC中設(shè)置client的超時時間

    這篇文章主要介紹了golang在GRPC中設(shè)置client的超時時間,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Go語言對JSON進行編碼和解碼的方法

    Go語言對JSON進行編碼和解碼的方法

    這篇文章主要介紹了Go語言對JSON進行編碼和解碼的方法,涉及Go語言操作json的技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02

最新評論