创建记录

  1. user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
  2. result := db.Create(&user) // 通过数据的指针来创建
  3. user.ID // 返回插入数据的主键
  4. result.Error // 返回 error
  5. result.RowsAffected // 返回插入记录的条数

我们还可以使用 Create() 创建多项记录:

  1. users := []*User{
  2. {Name: "Jinzhu", Age: 18, Birthday: time.Now()},
  3. {Name: "Jackson", Age: 19, Birthday: time.Now()},
  4. }
  5. result := db.Create(users) // pass a slice to insert multiple row
  6. result.Error // returns error
  7. result.RowsAffected // returns inserted records count

NOTE 你无法向 ‘create’ 传递结构体,所以你应该传入数据的指针.

用指定的字段创建记录

创建记录并为指定字段赋值。

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

创建记录并忽略传递给 ‘Omit’ 的字段值

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

批量插入

要高效地插入大量记录,请将切片传递给Create方法。 GORM 将生成一条 SQL 来插入所有数据,以返回所有主键值,并触发 Hook 方法。 当这些记录可以被分割成多个批次时,GORM会开启一个事务</0>来处理它们。

  1. var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
  2. db.Create(&users)
  3. for _, user := range users {
  4. user.ID // 1,2,3
  5. }

你可以通过db.CreateInBatches方法来指定批量插入的批次大小

  1. var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}
  2. // batch size 100
  3. db.CreateInBatches(users, 100)

UpsertCreate With Associations同样支持批量插入

注意 使用CreateBatchSize 选项初始化GORM实例后,此后进行创建& 关联操作时所有的INSERT行为都会遵循初始化时的配置。

  1. db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  2. CreateBatchSize: 1000,
  3. })
  4. db := db.Session(&gorm.Session{CreateBatchSize: 1000})
  5. users = [5000]User{{Name: "jinzhu", Pets: []Pet{pet1, pet2, pet3}}...}
  6. db.Create(&users)
  7. // INSERT INTO users xxx (5 batches)
  8. // INSERT INTO pets xxx (15 batches)

创建钩子

GORM允许用户通过实现这些接口 BeforeSave, BeforeCreate, AfterSave, AfterCreate来自定义钩子。 这些钩子方法会在创建一条记录时被调用,关于钩子的生命周期请参阅Hooks

  1. func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
  2. u.UUID = uuid.New()
  3. if u.Role == "admin" {
  4. return errors.New("invalid role")
  5. }
  6. return
  7. }

如果你想跳过Hooks方法,可以使用SkipHooks会话模式,例子如下

  1. DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)
  2. DB.Session(&gorm.Session{SkipHooks: true}).Create(&users)
  3. DB.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(users, 100)

根据 Map 创建

GORM支持通过 map[string]interface{}[]map[string]interface{}{}来创建记录。

  1. db.Model(&User{}).Create(map[string]interface{}{
  2. "Name": "jinzhu", "Age": 18,
  3. })
  4. // batch insert from `[]map[string]interface{}{}`
  5. db.Model(&User{}).Create([]map[string]interface{}{
  6. {"Name": "jinzhu_1", "Age": 18},
  7. {"Name": "jinzhu_2", "Age": 20},
  8. })

注意当使用map来创建时,钩子方法不会执行,关联不会被保存且不会回写主键。

使用 SQL 表达式、Context Valuer 创建记录

GORM允许使用SQL表达式来插入数据,有两种方法可以达成该目的,使用map[string]interface{} 或者 Customized Data Types, 示例如下:

  1. // Create from map
  2. db.Model(User{}).Create(map[string]interface{}{
  3. "Name": "jinzhu",
  4. "Location": clause.Expr{SQL: "ST_PointFromText(?)", Vars: []interface{}{"POINT(100 100)"}},
  5. })
  6. // INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"));
  7. // Create from customized data type
  8. type Location struct {
  9. X, Y int
  10. }
  11. // Scan implements the sql.Scanner interface
  12. func (loc *Location) Scan(v interface{}) error {
  13. // Scan a value into struct from database driver
  14. }
  15. func (loc Location) GormDataType() string {
  16. return "geometry"
  17. }
  18. func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
  19. return clause.Expr{
  20. SQL: "ST_PointFromText(?)",
  21. Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
  22. }
  23. }
  24. type User struct {
  25. Name string
  26. Location Location
  27. }
  28. db.Create(&User{
  29. Name: "jinzhu",
  30. Location: Location{X: 100, Y: 100},
  31. })
  32. // INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))

高级选项

关联创建

创建关联数据时,如果关联值非零,这些关联会被upsert,并且它们的Hooks方法也会被调用。

  1. type CreditCard struct {
  2. gorm.Model
  3. Number string
  4. UserID uint
  5. }
  6. type User struct {
  7. gorm.Model
  8. Name string
  9. CreditCard CreditCard
  10. }
  11. db.Create(&User{
  12. Name: "jinzhu",
  13. CreditCard: CreditCard{Number: "411111111111"},
  14. })
  15. // INSERT INTO `users` ...
  16. // INSERT INTO `credit_cards` ...

你可以通过Select, Omit方法来跳过关联更新,示例如下:

  1. db.Omit("CreditCard").Create(&user)
  2. // skip all associations
  3. db.Omit(clause.Associations).Create(&user)

默认值

你可以通过结构体Tag default来定义字段的默认值,示例如下:

  1. type User struct {
  2. ID int64
  3. Name string `gorm:"default:galeone"`
  4. Age int64 `gorm:"default:18"`
  5. }

这些默认值会被当作结构体字段的零值插入到数据库中

注意,当结构体的字段默认值是零值的时候比如 0, '', false,这些字段值将不会被保存到数据库中,你可以使用指针类型或者Scanner/Valuer来避免这种情况。

  1. type User struct {
  2. gorm.Model
  3. Name string
  4. Age *int `gorm:"default:18"`
  5. Active sql.NullBool `gorm:"default:true"`
  6. }

注意,若要让字段在数据库中拥有默认值则必须使用defaultTag来为结构体字段设置默认值。如果想要在数据库迁移的时候跳过默认值,可以使用 default:(-),示例如下:

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

注意 SQLite 不支持批量插入的时候使用默认值。 前往 SQLite Insert stmt了解。 下面是一个使用案例:

  1. type Pet struct {
  2. Name string gorm:"default:cat"
  3. }

// 在SqlLite中,这是不允许的, 所以GORM会通过构建错误的SQL来返回错误: // INSERT INTO pets (name) VALUES (“dog”),(DEFAULT) RETURNING name db.Create(&[]Pet{{Name: “dog”}, {}})

一个可行的替代方案是通过钩子方法来设置默认字段

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

你可以在issues#6335了解到更多有关信息。

当使用virtual/generated value时,你可能需要禁用它的创建/更新权限,前往Field-Level Permission了解字段权限。

Upsert 及冲突

GORM为不同数据库提供了对Upsert的兼容性支持。

  1. import "gorm.io/gorm/clause"
  2. // Do nothing on conflict
  3. db.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)
  4. // Update columns to default value on `id` conflict
  5. db.Clauses(clause.OnConflict{
  6. Columns: []clause.Column{{Name: "id"}},
  7. DoUpdates: clause.Assignments(map[string]interface{}{"role": "user"}),
  8. }).Create(&users)
  9. // MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET ***; SQL Server
  10. // INSERT INTO `users` *** ON DUPLICATE KEY UPDATE ***; MySQL
  11. // Use SQL expression
  12. db.Clauses(clause.OnConflict{
  13. Columns: []clause.Column{{Name: "id"}},
  14. DoUpdates: clause.Assignments(map[string]interface{}{"count": gorm.Expr("GREATEST(count, VALUES(count))")}),
  15. }).Create(&users)
  16. // INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `count`=GREATEST(count, VALUES(count));
  17. // Update columns to new value on `id` conflict
  18. db.Clauses(clause.OnConflict{
  19. Columns: []clause.Column{{Name: "id"}},
  20. DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
  21. }).Create(&users)
  22. // MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET "name"="excluded"."name"; SQL Server
  23. // INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age"; PostgreSQL
  24. // INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age); MySQL
  25. // Update all columns to new value on conflict except primary keys and those columns having default values from sql func
  26. db.Clauses(clause.OnConflict{
  27. UpdateAll: true,
  28. }).Create(&users)
  29. // INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age", ...;
  30. // INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age), ...; MySQL

前往Advanced Query了解有关FirstOrInit, FirstOrCreate的信息。

查看 Raw SQL and SQL Builder获取详情