对数据库进行添加,即新建操作。

这里以User类型作为操作对象:

type User struct {
    gorm.Model
    Name     string
    Age      int
    Birthday time.Time
}

先打开数据库:

dsn := "root:root@tcp(IP:PORT)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

创建记录

向数据库中添加一条新记录

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
    result := db.Create(&user)
    if result.Error != nil {
        t.Log("create record failed")
    } else {
        t.Logf("the primary key of inserted record is : %d", user.ID)
        t.Logf("affect rows is: %d", result.RowsAffected)
    }

直接使用Create即可创建记录,其返回值中Error记录了操作是否成功,RowsAffected记录了影响的数据库行号。

如果插入成功,那么对应的user对象中的ID就会被设置成对应的主键值。

如果在插入之前数据库表没有创建,那么插入会失败,可以通过AutoMigrate来自动创建对应的数据库表

db.AutoMigrate(&User{})

用选定的字段创建记录

对于一个model,可以只将其部分字段添加到数据库中创建新记录。

例如,下面创建一个记录并为指定的字段赋值。需要注意以下,虽然Select参数中没有提到的gorm.Model中的字段也会添加。

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

还可以使用Omit函数,效果跟Select相反,忽略传递给Omit的字段的值,也就是说Omit中的字段不赋值。

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", Birthday: time.Now()}, {Name: "jinzhu2", Birthday: time.Now()}, {Name: "jinzhu3", Birthday: time.Now()}}
 
result := db.Create(&users)

还可以使用CreateInBatches函数来分批次处理,可以自己指定每一批次的大小。

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

注意,如果在GORM初始化的时候就设置了CreateBatchSize选项,那么所有的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)

Create钩子函数

GORM允许用户自定义实现钩子函数BeforeSave,BeforeCreate,AfterSave,AfterCreate。通过钩子函数也可以明显感觉到创建记录的生命周期。

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {  
  u.UUID = uuid.New()  
  
  if u.Role == "admin" {  
    return errors.New("invalid role")  
  }  
  return  
}

使用SkipHooks的session模式,如果你想要跳过Hooks方法。

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创建时,钩子不会被调用,关联不会被保存,主键值不会被填充

根据SQL表达式创建

可以灵活定义字段插入时的数据格式。 使用SQL表达式也有两种方式,一种就是使用Map。

// 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)"));

还有就是使用自定义类型,例如下面的类型

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

GormValue中存在对应的SQL语句和vars。 在Create函数中会自动使用指定的SQL语句格式来进行创建值

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

进阶

创建关联数据

当创建一些带有关联的数据时,如果关联值不是零值,那么这些关联将同样被更新,其hook方法也会被调用。

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

可以通过使用Select,Omit来跳过保存关联数据。例如

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"`  
}

你必须为数据库中具有默认值的字段设置default标签,如果想要在migrate的时候跳过默认值定义,可以使用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:(-);"`  
}

Upsert与冲突

GORM为不同的数据库提供了兼容的upsert支持。 导入包:

import "gorm.io/gorm/clause"

冲突的时候什么都不做

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

id冲突的时候将列的值设置为默认值

db.Clauses(clause.OnConflict{  
  Columns:   []clause.Column{{Name: "id"}},  
  DoUpdates: clause.Assignments(map[string]interface{}{"role": "user"}),  
}).Create(&users)
// 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));

tags: gorm