创建记录

把数据存进数据库,是最基础的操作。GORM 提供了多种创建记录的方式,适应不同场景。

基本创建

定义好模型后,用 Create 方法插入:

type User struct {
    ID   uint
    Name string
    Age  int
}

user := User{Name: "张三", Age: 25}
result := db.Create(&user)

fmt.Println(user.ID)
fmt.Println(result.Error)
fmt.Println(result.RowsAffected)

注意 Create 的参数是指针。GORM 会回填主键 ID,所以传入指针才能拿到这个值。

result*gorm.DB 类型,可以获取:

  • Error - 错误信息
  • RowsAffected - 影响行数

用 Map 创建

不想定义结构体,或者只想插入部分字段,可以用 map:

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

批量插入多条:

users := []map[string]interface{}{
    {"Name": "用户1", "Age": 20},
    {"Name": "用户2", "Age": 25},
    {"Name": "用户3", "Age": 30},
}
db.Model(&User{}).Create(users)

批量插入

插入大量数据时,一条条插入效率太低:

users := []User{
    {Name: "用户1", Age: 20},
    {Name: "用户2", Age: 25},
    {Name: "用户3", Age: 30},
}
db.Create(&users)

GORM 会生成一条 INSERT 语句,包含所有数据。但不同数据库对单条 SQL 的参数数量有限制,数据量大时要分批:

db.CreateInBatches(users, 100)

每批 100 条,避免 SQL 过长。

选择性插入

只想插入部分字段,忽略其他:

db.Select("Name").Create(&User{
    Name: "王五",
    Age:  28,
})

生成的 SQL 只包含 name 字段,age 使用数据库默认值。

排除某些字段:

db.Omit("Age").Create(&User{
    Name: "赵六",
    Age:  28,
})

效果相同,只是写法不同。

创建时指定表名

临时指定表名:

db.Table("vip_users").Create(&user)

或者用 Model 方法:

db.Model(&User{}).Table("vip_users").Create(&user)

获取插入 ID

自增主键会自动回填:

user := User{Name: "测试"}
db.Create(&user)
fmt.Println(user.ID)

如果主键不是自增,比如 UUID,需要在创建前赋值:

import "github.com/google/uuid"

user := User{
    ID:   uuid.New(),
    Name: "测试",
}
db.Create(&user)

默认值

模型字段可以设置默认值:

type User struct {
    ID    uint
    Name  string
    Age   int    `gorm:"default:18"`
    Role  string `gorm:"default:'user'"`
}

插入时不指定这些字段,会使用默认值:

user := User{Name: "测试"}
db.Create(&user)

注意:default 标签的值是 SQL 层面的默认值,不是 Go 层面的。如果 Go 结构体字段有零值,GORM 仍然会插入这个零值,而不是使用数据库默认值。

要使用数据库默认值,需要显式忽略:

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

或者用 default 标签配合指针类型:

type User struct {
    Age *int `gorm:"default:18"`
}

On Conflict 处理

MySQL 的 ON DUPLICATE KEY UPDATE,PostgreSQL 的 ON CONFLICT,GORM 统一用 Clauses 处理:

import "gorm.io/gorm/clause"

user := User{ID: 1, Name: "更新后的名字", Age: 30}
db.Clauses(clause.OnConflict{
    Columns:   []clause.Column{{Name: "id"}},
    DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&user)

主键冲突时更新指定字段。

更新所有字段:

db.Clauses(clause.OnConflict{
    Columns:   []clause.Column{{Name: "id"}},
    DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&user)

不更新,直接忽略:

db.Clauses(clause.OnConflict{
    DoNothing: true,
}).Create(&user)

钩子函数

创建记录时会触发钩子:

func (u *User) BeforeCreate(tx *gorm.DB) error {
    if u.Name == "" {
        return errors.New("名字不能为空")
    }
    return nil
}

钩子返回错误会中止创建操作。

常见错误

主键冲突

Error 1062: Duplicate entry '1' for key 'PRIMARY'

主键已存在,使用 OnConflict 处理或检查后插入。

字段过长

Error 1406: Data too long for column 'name' at row 1

数据超出字段长度限制,检查模型定义或数据内容。

非空约束

Error 1048: Column 'name' cannot be null

必填字段未赋值,检查模型或添加默认值。

小结

创建记录看似简单,但批量插入、冲突处理、默认值这些细节,用好了能提升性能和代码质量。