Go的ORM框架的使用
特性
- 全功能 ORM
- 關(guān)聯(lián) (Has One,Has Many,Belongs To,Many To Many,多態(tài),單表繼承)
- Create,Save,Update,Delete,F(xiàn)ind 中鉤子方法
- 支持 Preload、Joins 的預(yù)加載
- 事務(wù),嵌套事務(wù),Save Point,Rollback To Saved Point
- Context、預(yù)編譯模式、DryRun 模式
- 批量插入,F(xiàn)indInBatches,F(xiàn)ind/Create with Map,使用 SQL 表達(dá)式、Context Valuer 進(jìn)行 CRUD
- SQL 構(gòu)建器,Upsert,數(shù)據(jù)庫鎖,Optimizer/Index/Comment Hint,命名參數(shù),子查詢
- 復(fù)合主鍵,索引,約束
- Auto Migration
- 自定義 Logger
- Generics API for type-safe queries and operations
- Extendable, flexible plugin API: Database Resolver (multiple databases, read/write splitting) / Prometheus…
- 每個(gè)特性都經(jīng)過了測試的重重考驗(yàn)
- 開發(fā)者友好
安裝
go get -u gorm.io/gorm go get -u gorm.io/driver/sqlite
快速入門
基礎(chǔ)的增刪改查
package main
import (
"context"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
ctx := context.Background()
// Migrate the schema
db.AutoMigrate(&Product{})
// Create
err = gorm.G[Product](db).Create(ctx, &Product{Code: "D42", Price: 100})
// Read
product, err := gorm.G[Product](db).Where("id = ?", 1).First(ctx) // find product with integer primary key
products, err := gorm.G[Product](db).Where("code = ?", "D42").Find(ctx) // find product with code D42
// Update - update product's price to 200
err = gorm.G[Product](db).Where("id = ?", product.ID).Update(ctx, "Price", 200)
// Update - update multiple fields
err = gorm.G[Product](db).Where("id = ?", product.ID).Updates(ctx, map[string]interface{}{"Price": 200, "Code": "F42"})
// Delete - delete product
err = gorm.G[Product](db).Where("id = ?", product.ID).Delete(ctx)
}
模型的定義
GORM 通過將 Go 結(jié)構(gòu)體(Go structs)可以理解為Java里的Do層映射到數(shù)據(jù)庫表來簡化數(shù)據(jù)庫交互。 了解如何在GORM中定義模型,是充分利用GORM全部功能的基礎(chǔ)。
模型是使用普通結(jié)構(gòu)體定義的。 這些結(jié)構(gòu)體可以包含具有基本Go類型、指針或這些類型的別名,甚至是自定義類型(只需要實(shí)現(xiàn) database/sql 包中的Scanner和Valuer接口)。
type User struct {
ID uint // 標(biāo)準(zhǔn)字段,用作主鍵
Name string // 普通字符串字段
Email *string // 指向字符串的指針,允許為空(NULL)
Age uint8 // 無符號8位整數(shù)
Birthday *time.Time // 指向時(shí)間的指針,可以為空(NULL)
MemberNumber sql.NullString // 使用 sql.NullString 來處理可為空的字符串
ActivatedAt sql.NullTime // 使用 sql.NullTime 來處理可為空的時(shí)間字段
CreatedAt time.Time // GORM 自動(dòng)管理的創(chuàng)建時(shí)間
UpdatedAt time.Time // GORM 自動(dòng)管理的更新時(shí)間
ignored string // 未導(dǎo)出的字段會被 GORM 忽略
}
在此模型中:
- 具體數(shù)字類型如 uint、string和 uint8 直接使用。
- 指向 *string 和 *time.Time 類型的指針表示可空字段。
- 來自 database/sql 包的 sql.NullString 和 sql.NullTime 用于具有更多控制的可空字段。
- CreatedAt 和 UpdatedAt 是特殊字段,當(dāng)記錄被創(chuàng)建或更新時(shí),GORM 會自動(dòng)向內(nèi)填充當(dāng)前時(shí)間。
- Non-exported fields (starting with a small letter) are not mapped
約定
- 主鍵:GORM 使用一個(gè)名為ID 的字段作為每個(gè)模型的默認(rèn)主鍵。
- 表名:默認(rèn)情況下,GORM 將結(jié)構(gòu)體名稱轉(zhuǎn)換為 snake_case 并為表名加上復(fù)數(shù)形式。
例如,一個(gè) User 結(jié)構(gòu)體在數(shù)據(jù)庫中對應(yīng)的表名會變成 users,而 GormUserName 則會變成 gorm_user_names。
- 列名:GORM 自動(dòng)將結(jié)構(gòu)體字段名稱轉(zhuǎn)換為 snake_case 作為數(shù)據(jù)庫中的列名。
- 時(shí)間戳字段:GORM使用字段 CreatedAt 和 UpdatedAt 來自動(dòng)跟蹤記錄的創(chuàng)建和更新時(shí)間。
遵循這些約定可以大大減少您需要編寫的配置或代碼量。 但是,GORM也具有靈活性,允許根據(jù)自己的需求自定義這些設(shè)置。
示例
- 列名轉(zhuǎn)換
| 結(jié)構(gòu)體字段 | 數(shù)據(jù)庫列名 | 說明 |
|---|---|---|
| ID | id | 默認(rèn)主鍵 |
| Name | name | 轉(zhuǎn)換為 snake_case |
| 轉(zhuǎn)換為 snake_case | ||
| Age | age | 轉(zhuǎn)換為 snake_case |
| CreatedAt | created_at | 時(shí)間戳字段 |
| UpdatedAt | updated_at | 時(shí)間戳字段 |
- 表名轉(zhuǎn)換
| 結(jié)構(gòu)體名稱 | 數(shù)據(jù)庫表名 | 說明 |
|---|---|---|
| User | users | 結(jié)構(gòu)體名轉(zhuǎn) snake_case + 復(fù)數(shù) |
| GormUserName | gorm_user_names | 結(jié)構(gòu)體名轉(zhuǎn) snake_case + 復(fù)數(shù) |
| Product | products | 結(jié)構(gòu)體名轉(zhuǎn) snake_case + 復(fù)數(shù) |
| OrderItem | order_items | 結(jié)構(gòu)體名轉(zhuǎn) snake_case + 復(fù)數(shù) |
自定義約定示例
如果需要自定義表名,可以實(shí)現(xiàn)TableName方法:
func (User) TableName() string {
return "user_profiles" // 自定義表名
}
或者通過配置全局修改:
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: "tbl_", // 表名前綴
SingularTable: true, // 使用單數(shù)表名
NoLowerCase: false, // 使用小寫
NameReplacer: strings.NewReplacer("ID", "Id"), // 替換特定名稱
},
})
gorm.Model
GORM提供了一個(gè)預(yù)定義的結(jié)構(gòu)體,名為gorm.Model,其中包含常用字段:
// gorm.Model 的定義
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
- ID :每個(gè)記錄的唯一標(biāo)識符(主鍵)。
- CreatedAt :在創(chuàng)建記錄時(shí)自動(dòng)設(shè)置為當(dāng)前時(shí)間。
- UpdatedAt:每當(dāng)記錄更新時(shí),自動(dòng)更新為當(dāng)前時(shí)間。
- DeletedAt:用于軟刪除(將記錄標(biāo)記為已刪除,而實(shí)際上并未從數(shù)據(jù)庫中刪除)。
使用方式如下:
package model
import (
"database/sql"
"time"
"gorm.io/gorm"
)
type User struct {
gorm.Model
ID uint // 標(biāo)準(zhǔn)字段,用作主鍵
Name string // 普通字符串字段
Email *string // 指向字符串的指針,允許為空(NULL)
Age uint8 // 無符號8位整數(shù)
Birthday *time.Time // 指向時(shí)間的指針,可以為空(NULL)
MemberNumber sql.NullString // 使用 sql.NullString 來處理可為空的字符串
ActivatedAt sql.NullTime // 使用 sql.NullTime 來處理可為空的時(shí)間字段
ignored string // 未導(dǎo)出的字段會被 GORM 忽略
}
字段級權(quán)限控制
可導(dǎo)出的字段在使用 GORM 進(jìn)行 CRUD 時(shí)擁有全部的權(quán)限,此外,GORM 允許您用標(biāo)簽控制字段級別的權(quán)限。這樣您就可以讓一個(gè)字段的權(quán)限是只讀、只寫、只創(chuàng)建、只更新或者被忽略
type User struct {
Name string `gorm:"<-:create"` // 允許讀和創(chuàng)建
Name string `gorm:"<-:update"` // 允許讀和更新
Name string `gorm:"<-"` // 允許讀和寫(創(chuàng)建和更新)
Name string `gorm:"<-:false"` // 允許讀,禁止寫
Name string `gorm:"->"` // 只讀(除非有自定義配置,否則禁止寫)
Name string `gorm:"->;<-:create"` // 允許讀和寫
Name string `gorm:"->:false;<-:create"` // 僅創(chuàng)建(禁止從 db 讀)
Name string `gorm:"-"` // 通過 struct 讀寫會忽略該字段
Name string `gorm:"-:all"` // 通過 struct 讀寫、遷移會忽略該字段
Name string `gorm:"-:migration"` // 通過 struct 遷移會忽略該字段
}
嵌入結(jié)構(gòu)體
- 方式1:直接嵌入
type Author struct {
Name string
Email string
}
type Blog struct {
Author
ID int
Upvotes int32
}
// equals
type Blog struct {
ID int64
Name string
Email string
Upvotes int32
}
- 通過標(biāo)簽 embedded嵌入
type Author struct {
Name string
Email string
}
type Blog struct {
ID int
Author Author `gorm:"embedded"`
Upvotes int32
}
// 等效于
type Blog struct {
ID int64
Name string
Email string
Upvotes int32
}
- 使用標(biāo)簽 embeddedPrefix 來為 db 中的字段名添加前綴
type Blog struct {
ID int
Author Author `gorm:"embedded;embeddedPrefix:author_"`
Upvotes int32
}
// 等效于
type Blog struct {
ID int64
AuthorName string
AuthorEmail string
Upvotes int32
}
聲明模型時(shí),標(biāo)簽是可選的,GORM支持以下標(biāo)簽:標(biāo)簽不區(qū)分大小寫,但建議使用駝峰式命名法(camelCase)。如果使用多個(gè)標(biāo)簽,則應(yīng)使用分號(;)分隔。對于解析器具有特殊含義的字符可以使用反斜杠(1)進(jìn)行轉(zhuǎn)義,以便將其用作參數(shù)值。
| 標(biāo)簽名 | 說明 |
|---|---|
| column | 指定 db 列名 |
| type | 列數(shù)據(jù)類型,推薦使用兼容性好的通用類型,例如:所有數(shù)據(jù)庫都支持 bool、int、uint、float、string、time、bytes,并且可以和其他標(biāo)簽一起使用,例如:not null、size、autoIncrement … 像 varbinary(8) 這樣指定數(shù)據(jù)庫數(shù)據(jù)類型也是支持的。在使用指定數(shù)據(jù)庫數(shù)據(jù)類型時(shí),它需要是完整的數(shù)據(jù)庫數(shù)據(jù)類型,如:MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT |
| serializer | 指定將數(shù)據(jù)序列化或反序列化到數(shù)據(jù)庫中的序列化器,例如:serializer:json/gob/unixtime |
| size | 定義列數(shù)據(jù)類型的大小或長度,例如:size: 256 |
| primaryKey | 將列定義為主鍵 |
| unique | 將列定義為唯一鍵 |
| default | 定義列的默認(rèn)值 |
| precision | 指定列的精度 |
| scale | 指定列大小 |
| not null | 指定列為 NOT NULL |
| autoIncrement | 指定列為自動(dòng)增長 |
| autoIncrementIncrement | 自動(dòng)步長,控制連續(xù)記錄之間的間隔 |
| embedded | 嵌套字段 |
| embeddedPrefix | 嵌入字段的列名前綴 |
| autoCreateTime | 創(chuàng)建時(shí)追蹤當(dāng)前時(shí)間,對于 int 字段,它會追蹤時(shí)間戳秒數(shù),您可以使用 nano / milli 來追蹤納秒、毫秒時(shí)間戳,例如:autoCreateTime:nano |
| autoUpdateTime | 創(chuàng)建/更新時(shí)追蹤當(dāng)前時(shí)間,對于 int 字段,它會追蹤時(shí)間戳秒數(shù),您可以使用 nano / milli 來追蹤納秒、毫秒時(shí)間戳,例如:autoUpdateTime:milli |
| index | 根據(jù)參數(shù)創(chuàng)建索引,多個(gè)字段使用相同的名稱則創(chuàng)建復(fù)合索引,查看 索引 獲取詳情 |
| uniqueIndex | 與 index 相同,但創(chuàng)建的是唯一索引 |
| check | 創(chuàng)建檢查約束,例如 check:age > 13,查看 約束 獲取詳情 |
| <- | 設(shè)置字段寫入的權(quán)限,<-:create 只創(chuàng)建、<-:update 只更新、<-:false 無寫入權(quán)限、<- 創(chuàng)建和更新權(quán)限 |
| -> | 設(shè)置字段讀的權(quán)限,->:false 無讀權(quán)限 |
| - | 忽略該字段,- 表示無讀寫,-:migration 表示無遷移權(quán)限,-:all 表示無讀寫遷移權(quán)限 |
| comment | 遷移時(shí)為字段添加注釋 |
連接數(shù)據(jù)庫
最基礎(chǔ)的用法
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// 參考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 獲取詳情
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{})
}
CRUD
package main
import (
"fmt"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// 定義符合 GORM 約定的 User 模型(嵌入 gorm.Model)
type User struct {
gorm.Model // 自動(dòng)包含 ID, CreatedAt, UpdatedAt, DeletedAt
Name string `gorm:"size:255"` // 可指定字段長度
Email *string `gorm:"unique"` // 唯一索引
Age uint8 `gorm:"default:18"` // 默認(rèn)值
Birthday *time.Time // 指向時(shí)間的指針
MemberNumber sql.NullString // 處理可為空的字符串
ActivatedAt sql.NullTime // 處理可為空的時(shí)間字段
ignored string // 未導(dǎo)出字段會被 GORM 忽略
}
func main() {
// 1. 連接 MySQL 數(shù)據(jù)庫
dsn := "root:root@tcp(127.0.0.1:3306)/gorm_demo?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("數(shù)據(jù)庫連接失敗: " + err.Error())
}
// 2. 自動(dòng)遷移(創(chuàng)建表)
db.AutoMigrate(&User{})
// 3. 創(chuàng)建操作 (Create)
fmt.Println("\n=== 創(chuàng)建用戶 ===")
user1 := User{
Name: "張三",
Email: &[]string{"zhangsan@example.com"}[0],
Age: 30,
Birthday: &time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
MemberNumber: sql.NullString{String: "M12345", Valid: true},
ActivatedAt: sql.NullTime{Time: time.Now(), Valid: true},
}
if err := db.Create(&user1).Error; err != nil {
panic("創(chuàng)建用戶失敗: " + err.Error())
}
fmt.Printf("創(chuàng)建成功! ID: %d\n", user1.ID)
// 4. 查詢操作 (Read)
fmt.Println("\n=== 查詢用戶 ===")
var user2 User
// 查詢單個(gè)記錄
if err := db.First(&user2, user1.ID).Error; err != nil {
panic("查詢用戶失敗: " + err.Error())
}
fmt.Printf("查詢到用戶: %s (ID: %d, 年齡: %d)\n", user2.Name, user2.ID, user2.Age)
// 查詢多個(gè)記錄
var users []User
db.Where("age > ?", 25).Find(&users)
fmt.Printf("年齡大于25的用戶: %d 個(gè)\n", len(users))
// 使用條件查詢
var user3 User
db.Where("name = ?", "張三").First(&user3)
fmt.Printf("條件查詢: %s (ID: %d)\n", user3.Name, user3.ID)
// 5. 更新操作 (Update)
fmt.Println("\n=== 更新用戶 ===")
user2.Age = 31
if err := db.Save(&user2).Error; err != nil {
panic("更新用戶失敗: " + err.Error())
}
fmt.Printf("更新成功! 年齡: %d\n", user2.Age)
// 更新特定字段
db.Model(&user2).Update("age", 32)
fmt.Printf("更新特定字段: 年齡: %d\n", user2.Age)
// 6. 刪除操作 (Delete)
fmt.Println("\n=== 刪除用戶 ===")
// 軟刪除(默認(rèn))
if err := db.Delete(&user2).Error; err != nil {
panic("軟刪除失敗: " + err.Error())
}
fmt.Printf("軟刪除成功! ID: %d\n", user2.ID)
// 硬刪除(需要設(shè)置 DeleteMode 為 HardDelete)
if err := db.Unscoped().Delete(&user2).Error; err != nil {
panic("硬刪除失敗: " + err.Error())
}
fmt.Printf("硬刪除成功! ID: %d\n", user2.ID)
// 7. 其他常用操作
fmt.Println("\n=== 其他常用操作 ===")
// 創(chuàng)建或更新(如果存在則更新,不存在則創(chuàng)建)
user4 := User{Name: "李四", Age: 25}
db.FirstOrCreate(&user4, User{Name: "李四"})
fmt.Printf("創(chuàng)建或更新: %s (ID: %d)\n", user4.Name, user4.ID)
// 通過 ID 查詢
var user5 User
db.First(&user5, 1) // 通過 ID 查詢
fmt.Printf("通過 ID 查詢: %s\n", user5.Name)
// 分頁查詢
var usersPage []User
db.Limit(2).Offset(0).Find(&usersPage)
fmt.Printf("分頁查詢: %d 個(gè)用戶\n", len(usersPage))
// 計(jì)數(shù)
var count int64
db.Model(&User{}).Where("age > ?", 25).Count(&count)
fmt.Printf("年齡大于25的用戶總數(shù): %d\n", count)
}
在使用 GORM 的 AutoMigrate 功能時(shí),無需手動(dòng)創(chuàng)建數(shù)據(jù)庫表,只需定義好 Go 結(jié)構(gòu)體(模型),然后調(diào)用 AutoMigrate 方法,GORM 會自動(dòng)根據(jù)結(jié)構(gòu)體定義創(chuàng)建數(shù)據(jù)庫表。
方式2:
package main
import (
"fmt"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// 自定義表名模型(演示如何指定表名)
type EntityRobot struct {
ID uint `gorm:"primary_key"`
EntityID uint `gorm:"index:idx_entity_biz"`
BizType string `gorm:"size:50;index:idx_entity_biz"`
Params map[string]interface{} `gorm:"type:json"` // 使用JSON存儲參數(shù)
CreatedAt time.Time
UpdatedAt time.Time
}
// 實(shí)現(xiàn)TableName方法指定表名
func (EntityRobot) TableName() string {
return "entity_robot" // 自定義表名,與數(shù)據(jù)庫表名一致
}
func main() {
// 1. 數(shù)據(jù)庫連接
dsn := "root:root@tcp(127.0.0.1:3306)/gorm_demo?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("數(shù)據(jù)庫連接失敗: " + err.Error())
}
// 2. 自動(dòng)遷移(創(chuàng)建表)
db.AutoMigrate(&EntityRobot{})
// 3. 創(chuàng)建測試數(shù)據(jù)
robot := EntityRobot{
EntityID: 1001,
BizType: "user_profile",
Params: map[string]interface{}{"name": "張三", "age": 30},
}
db.Create(&robot)
// 4. 演示您的示例:指定表名 + 條件更新
entityId := uint(1001)
bizType := "user_profile"
params := map[string]interface{}{
"params": map[string]interface{}{"name": "李四", "age": 32},
}
// 使用 Table() 指定表名 + Where 條件 + Update
result := db.Table("entity_robot").
Where("entity_id = ? AND biz_type = ?", entityId, bizType).
Updates(params)
// 檢查更新結(jié)果
fmt.Printf("更新影響行數(shù): %d\n", result.RowsAffected)
if result.Error != nil {
panic("更新失敗: " + result.Error.Error())
}
// 5. 驗(yàn)證更新結(jié)果
var updatedRobot EntityRobot
db.Where("entity_id = ? AND biz_type = ?", entityId, bizType).First(&updatedRobot)
fmt.Printf("更新后參數(shù): %+v\n", updatedRobot.Params)
// 6. 其他常用操作演示
// 6.1 創(chuàng)建(帶JSON參數(shù))
newRobot := EntityRobot{
EntityID: 1002,
BizType: "order",
Params: map[string]interface{}{"order_id": "ORD1001", "amount": 199.9},
}
db.Create(&newRobot)
// 6.2 查詢(帶條件)
var robots []EntityRobot
db.Where("biz_type = ?", "order").Find(&robots)
fmt.Printf("查詢到 %d 個(gè)訂單記錄\n", len(robots))
// 6.3 軟刪除(默認(rèn))
db.Delete(&newRobot)
// 6.4 硬刪除(需要使用 Unscoped)
db.Unscoped().Delete(&newRobot)
// 6.5 批量更新(更新多個(gè)記錄)
db.Table("entity_robot").
Where("biz_type = ?", "user_profile").
Updates(map[string]interface{}{
"params": map[string]interface{}{"status": "active"},
})
// 6.6 原生SQL查詢
var rawResult []struct {
EntityID uint
BizType string
}
db.Raw("SELECT entity_id, biz_type FROM entity_robot WHERE params->>'$.age' > ?", 30).Scan(&rawResult)
fmt.Printf("查詢到 %d 條年齡>30的記錄\n", len(rawResult))
}
這種方式也是我們項(xiàng)目中常用的一個(gè)操作
到此這篇關(guān)于Go的ORM框架的使用的文章就介紹到這了,更多相關(guān)Go ORM框架內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺析如何利用Go的plugin機(jī)制實(shí)現(xiàn)熱更新
熱更新,或稱熱重載或動(dòng)態(tài)更新,是一種軟件更新技術(shù),允許程序在運(yùn)行時(shí),不停機(jī)更新代碼或資源,本文主要來討論下GO語言是否可以利用plugin機(jī)制實(shí)現(xiàn)熱更新,感興趣的可以了解下2024-04-04
Go Web后臺管理系統(tǒng)項(xiàng)目實(shí)現(xiàn)
本文主要介紹了Go Web后臺管理系統(tǒng)項(xiàng)目實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-05-05
golang?select?機(jī)制和超時(shí)問題
golang 中的協(xié)程使用非常方便,但是協(xié)程什么時(shí)候結(jié)束是一個(gè)控制問題,可以用 select 配合使用,這篇文章主要介紹了golang?select?機(jī)制和超時(shí)問題,需要的朋友可以參考下2022-06-06
golang?防緩存擊穿singleflight的實(shí)現(xiàn)
本文主要介紹了golang?防緩存擊穿singleflight的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
golang 對私有函數(shù)進(jìn)行單元測試的實(shí)例
這篇文章主要介紹了golang 對私有函數(shù)進(jìn)行單元測試的實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-05-05

