建立

建立記錄

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

result := db.Create(&user) // pass pointer of data to Create

user.ID // returns inserted data's primary key
result.Error // returns error
result.RowsAffected // returns inserted records count

我們也可以使用 Create() 建立多筆記錄

users := []*User{
{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
{Name: "Jackson", Age: 19, Birthday: time.Now()},
}

result := db.Create(users) // pass a slice to insert multiple row

result.Error // returns error
result.RowsAffected // returns inserted records count

注意 您無法傳遞結構給「建立」,所以您應該傳遞資料的指標。

使用選取欄位建立記錄

建立記錄並將值指定給指定的欄位。

db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")

建立記錄並忽略傳遞的欄位值。

db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")

批次新增

若要有效率地新增大量記錄,請將片段傳遞給 Create 方法。GORM 會產生單一 SQL 陳述式來新增所有資料並填入主鍵值,也會呼叫掛勾方法。當記錄可以分割成多個批次時,它會開始一個交易

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
user.ID // 1,2,3
}

您可以在使用 CreateInBatches 建立時指定批次大小,例如

var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}

// batch size 100
db.CreateInBatches(users, 100)

使用 Upsert使用關聯建立 時也支援批次新增

注意 使用 CreateBatchSize 選項初始化 GORM,所有 INSERT 在建立記錄和關聯時都會尊重此選項

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
CreateBatchSize: 1000,
})

db := db.Session(&gorm.Session{CreateBatchSize: 1000})

users = [5000]User{{Name: "jinzhu", Pets: []Pet{pet1, pet2, pet3}}...}

db.Create(&users)
// INSERT INTO users xxx (5 batches)
// INSERT INTO pets xxx (15 batches)

建立掛勾

GORM 允許使用者定義掛勾來實作 BeforeSaveBeforeCreateAfterSaveAfterCreate。這些掛勾方法會在建立記錄時呼叫,有關生命週期的詳細資訊,請參閱 掛勾

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.UUID = uuid.New()

if u.Role == "admin" {
return errors.New("invalid role")
}
return
}

如果您想要跳過 Hooks 方法,您可以使用 SkipHooks 會話模式,例如

DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)

DB.Session(&gorm.Session{SkipHooks: true}).Create(&users)

DB.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(users, 100)

從 Map 建立

GORM 支援從 map[string]interface{}[]map[string]interface{}{} 建立,例如

db.Model(&User{}).Create(map[string]interface{}{
"Name": "jinzhu", "Age": 18,
})

// batch insert from `[]map[string]interface{}{}`
db.Model(&User{}).Create([]map[string]interface{}{
{"Name": "jinzhu_1", "Age": 18},
{"Name": "jinzhu_2", "Age": 20},
})

注意 從 map 建立時,不會呼叫 hooks,關聯不會儲存,主鍵值也不會填回

從 SQL 表達式/Context Valuer 建立

GORM 允許使用 SQL 表達式插入資料,有兩種方法可以達成此目標,從 map[string]interface{}自訂資料類型 建立,例如

// Create from map
db.Model(User{}).Create(map[string]interface{}{
"Name": "jinzhu",
"Location": clause.Expr{SQL: "ST_PointFromText(?)", Vars: []interface{}{"POINT(100 100)"}},
})
// INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"));

// Create from customized data type
type Location struct {
X, Y int
}

// Scan implements the sql.Scanner interface
func (loc *Location) Scan(v interface{}) error {
// Scan a value into struct from database driver
}

func (loc Location) GormDataType() string {
return "geometry"
}

func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
return clause.Expr{
SQL: "ST_PointFromText(?)",
Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
}
}

type User struct {
Name string
Location Location
}

db.Create(&User{
Name: "jinzhu",
Location: Location{X: 100, Y: 100},
})
// INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))

進階

建立關聯

在建立資料與關聯時,如果其關聯值不是零值,這些關聯將會被更新或插入,並且其 Hooks 方法將被呼叫。

type CreditCard struct {
gorm.Model
Number string
UserID uint
}

type User struct {
gorm.Model
Name string
CreditCard CreditCard
}

db.Create(&User{
Name: "jinzhu",
CreditCard: CreditCard{Number: "411111111111"}
})
// INSERT INTO `users` ...
// INSERT INTO `credit_cards` ...

您可以使用 SelectOmit 來略過儲存關聯,例如

db.Omit("CreditCard").Create(&user)

// skip all associations
db.Omit(clause.Associations).Create(&user)

預設值

您可以使用標籤 default 為欄位定義預設值,例如

type User struct {
ID int64
Name string `gorm:"default:galeone"`
Age int64 `gorm:"default:18"`
}

然後,在插入資料庫時,預設值將會用於零值欄位

注意 對於定義預設值的欄位,任何零值(例如 0''false)都不會儲存到資料庫中,您可能需要使用指標類型或 Scanner/Valuer 來避免此情況,例如

type User struct {
gorm.Model
Name string
Age *int `gorm:"default:18"`
Active sql.NullBool `gorm:"default:true"`
}

注意 如果您想要在遷移時略過預設值定義,您必須為資料庫中具有預設值或虛擬/產生值的欄位設定 default 標籤,您可以使用 default:(-),例如

type User struct {
ID string `gorm:"default:uuid_generate_v3()"` // db func
FirstName string
LastName string
Age uint8
FullName string `gorm:"->;type:GENERATED ALWAYS AS (concat(firstname,' ',lastname));default:(-);"`
}

注意 SQLite 在批次插入時不支援某些記錄為預設值。
請參閱 SQLite Insert stmt。例如

type Pet struct {
Name string `gorm:"default:cat"`
}

// In SQLite, this is not supported, so GORM will build a wrong SQL to raise error:
// INSERT INTO `pets` (`name`) VALUES ("dog"),(DEFAULT) RETURNING `name`
db.Create(&[]Pet{{Name: "dog"}, {}})

一個可行的替代方案是在 hook 中指定欄位的預設值,例如

func (p *Pet) BeforeCreate(tx *gorm.DB) (err error) {
if p.Name == "" {
p.Name = "cat"
}
}

您可以在 issues#6335 中看到更多資訊

在使用虛擬/產生值時,您可能需要停用其建立/更新權限,請查看 欄位層級權限

Upsert / On Conflict

GORM 為不同的資料庫提供相容的 Upsert 支援

import "gorm.io/gorm/clause"

// Do nothing on conflict
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)

// Update columns to default value on `id` conflict
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.Assignments(map[string]interface{}{"role": "user"}),
}).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET ***; SQL Server
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE ***; MySQL

// Use SQL expression
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.Assignments(map[string]interface{}{"count": gorm.Expr("GREATEST(count, VALUES(count))")}),
}).Create(&users)
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `count`=GREATEST(count, VALUES(count));

// Update columns to new value on `id` conflict
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET "name"="excluded"."name"; SQL Server
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age"; PostgreSQL
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age); MySQL

// Update all columns to new value on conflict except primary keys and those columns having default values from sql func
db.Clauses(clause.OnConflict{
UpdateAll: true,
}).Create(&users)
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age", ...;
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age), ...; MySQL

另外,請在 進階查詢 中查看 FirstOrInitFirstOrCreate

請查看 原始 SQL 和 SQL Builder 以取得更多詳細資訊

白金贊助商

金牌贊助商

白金贊助商

金牌贊助商