创建记录

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

    result := db.Create(&user) // 通过数据的指针来创建

    user.ID // 返回插入数据的主键
    result.Error // 返回 error
    result.RowsAffected // 返回插入记录的条数

用指定的字段创建记录

创建记录并更新给出的字段。

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

创建记录并更新未给出的字段。

  1. 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")

批量插入

要有效地插入大量记录,请将一个 slice 传递给 Create 方法。 将切片数据传递给 Create 方法,GORM 将生成一个单一的 SQL 语句来插入所有数据,并回填主键的值,钩子方法也会被调用。

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

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

使用 CreateInBatches 创建时,你还可以指定创建的数量,例如:

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

    // 数量为 100
    db.CreateInBatches(users, 100)

UpsertCreate With Associations 也支持批量插入

NOTE initialize GORM with CreateBatchSize option, all INSERT will respect this option when creating record & associations

  1. 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 allows user defined hooks to be implemented for BeforeSave, BeforeCreate, AfterSave, AfterCreate. These hook method will be called when creating a record, refer Hooks for details on the lifecycle

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

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

If you want to skip Hooks methods, you can use the SkipHooks session mode, for example:

  1. 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 supports create from map[string]interface{} and []map[string]interface{}{}, e.g:

  1. 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},
    })

NOTE When creating from map, hooks won’t be invoked, associations won’t be saved and primary key values won’t be back filled

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

GORM allows insert data with SQL expression, there are two ways to achieve this goal, create from map[string]interface{} or Customized Data Types, for example:

  1. // 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)"))

高级选项

关联创建

When creating some data with associations, if its associations value is not zero-value, those associations will be upserted, and its Hooks methods will be invoked.

  1. 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` ...

You can skip saving associations with Select, Omit, for example:

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

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

默认值

You can define default values for fields with tag default, for example:

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

Then the default value will be used when inserting into the database for zero-value fields

NOTE Any zero value like 0, '', false won’t be saved into the database for those fields defined default value, you might want to use pointer type or Scanner/Valuer to avoid this, for example:

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

NOTE You have to setup the default tag for fields having default or virtual/generated value in database, if you want to skip a default value definition when migrating, you could use default:(-), for example:

  1. 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:(-);`
    }

When using virtual/generated value, you might need to disable its creating/updating permission, check out Field-Level Permission

Upsert 及冲突

GORM provides compatible Upsert support for different databases

  1. 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

    // 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 expects primary keys to new value on conflict
    db.Clauses(clause.OnConflict{
    UpdateAll: true,
    }).Create(&users)
    // INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age", ...;

Also checkout FirstOrInit, FirstOrCreate on Advanced Query

Checkout Raw SQL and SQL Builder for more details