插入数据

根据struct进行插入
演示了插入一条数据,插入多条数据,分批插入数据

func Create(db *gorm.DB) {
    //插入一条记录
    user := User{UserId: rand.IntN(100000), Degree: "本科", Gender: "男", City: "上海", Keywords: []string{"编程", "golang"}}
    result := db.Create(&user) //必须传指针,因为要给User的主键赋值。主键为0值时Create会自动给主键赋值,也会给CreatedAt字段赋值
    if result.Error != nil {
        slog.Error("插入记录失败", "error", result.Error)
    }
    fmt.Printf("record id is %d\n", user.Id)
    fmt.Printf("影响%d行\n", result.RowsAffected)

    //会话模式
    tx := db.Session(&gorm.Session{SkipHooks: true}) //不执行钩子Hook
    // db := db.Session(&gorm.Session{DryRun: true}) //生成SQL,但不执行
    //一次性插入多条
    user1 := user                    //发生拷贝
    user1.Id = 0                     //把主键置为0,因为user.Id是有值的
    user1.UserId = rand.IntN(100000) //UserId上有唯一性约束
    user2 := user                    //发生拷贝
    user2.Id = 0                     //把主键置为0
    user2.UserId = rand.IntN(100000)
    users := []*User{&user1, &user2} //切片里的元素也可以不是指针
    result = tx.Create(users)        //一条SQL插入所有数据
    fmt.Printf("影响%d行\n", result.RowsAffected)

    //量太大时分批插入(SQL语句的长度是有上限的,同时避免长时间阻塞)
    batchSize := 1 //通常为几百
    user3 := user
    user3.Id = 0
    user3.UserId = rand.IntN(100000)
    user4 := user3//由于没有设置user3=4.Id = 0,会插不进去
    user4.Id = 0
    db.CreateInBatches([]*User{&user3, &user4}, batchSize) //一个批次一条SQL。且所有批次被放到一个事务中来执行。由于user4插不进去,所以user3也会回滚。但如果设置了SkipDefaultTransaction就没有事务
}

根据map进行插入
演示了插入一条数据和插入多条数据

func CreateByMap(db *gorm.DB) error {
    //插入一条记录
    err := db.Model(User{}).Create(map[string]any{ // 不会自动给Id和CreatedAt赋值,毕竟也没给Create函数传结构体指针
        "uid": rand.IntN(100000), "degree": "本科", "gender": "男", "city": "上海",
        "create_time": time.Now(),
    }).Error
    if err != nil {
        return err
    }

    //一次性插入多条
    err = db.Model(User{}).Create([]map[string]any{
        {"uid": rand.IntN(100000), "degree": "本科", "gender": "男", "city": "北京", "create_time": time.Now()},
        {"uid": rand.IntN(100000), "degree": "本科", "gender": "男", "city": "深圳", "create_time": time.Now()},
    }).Error
    if err != nil {
        return err
    }

    return nil
}

钩子

如果任何钩子回调返回错误,GORM将停止后续的操作并回滚事务。

Create时钩子的执行时机:

开始事务
BeforeSave (Create/Update 都会触发)
BeforeCreate
关联前的 save
插入记录至 db
关联后的 save
AfterCreate
AfterSave (Create/Update 都会触发)

提交或回滚事务

func (u *User) BeforeSave(db *gorm.DB) (err error) {
    db.Logger.Info(context.Background(), "exec hook BeforeSave")
    return nil
}

func (u *User) BeforeCreate(db *gorm.DB) (err error) {
    db.Logger.Info(context.Background(), "exec hook BeforeCreate")
    return nil
}

func (u *User) AfterCreate(db *gorm.DB) (err error) {
    db.Logger.Info(context.Background(), "exec hook AfterCreate")
    return nil
}

func (u *User) AfterSave(db *gorm.DB) (err error) {
    db.Logger.Info(context.Background(), "exec hook AfterSave")
    return nil
}

删除数据

func Delete(db *gorm.DB) {
    tx := db.Where("degree=?", "本科").Delete(User{})
    fmt.Printf("删除%d行\n", tx.RowsAffected)

    var user User = User{Id: 10}
    db.Delete(user) //暗含的Where条件是id=10

    db.Delete(User{}, 1)              //暗含的Where条件是id=1
    db.Delete(User{}, []int{1, 2, 3}) //暗含的Where条件是id IN (1,2,3)
}

钩子

如果任何钩子回调返回错误,GORM将停止后续的操作并回滚事务。

Delete时钩子的执行时机:
开始事务
BeforeDelete
删除 db 中的数据
AfterDelete
提交或回滚事务

改变数据

save

// Save会保存所有的字段,即使字段是零值。主键为0时Save相当于Create
func Save(db *gorm.DB) {
    user := User{UserId: rand.IntN(100000), Degree: "本科", Gender: "男", City: "上海"}
    db.Save(&user) //主键为0值,Save相当于Create

    var user2 User
    db.Last(&user2)
    user2.Degree = "硕士"
    db.Save(&user2) //必须传指针
}

Update指定需要更新的列

func Update(db *gorm.DB) {
    // 根据map更新
    tx := db.Model(&User{}). //必须传指针
                    Where("city=?", "北京").Updates(
        map[string]any{"degree": "硕士", "gender": "男"},
    )
    fmt.Printf("更新了%d行\n", tx.RowsAffected)

    //根据结构体更新,只会更新非0值
    db.Model(&User{}). //必须传指针
                Where("city=?", "北京").Updates(
        User{Degree: "本科", Gender: "男", Id: 1},
    )
    fmt.Printf("更新了%d行\n", tx.RowsAffected)
}

钩子

如果任何钩子回调返回错误,GORM将停止后续的操作并回滚事务。

Update时钩子的执行时机:

开始事务
BeforeSave
BeforeUpdate
关联前的 save
更新 db
关联后的 save
AfterUpdate
AfterSave

提交或回滚事务

查询数据

func Read(db *gorm.DB) {
    user := User{City: "HongKong", Id: 3} //Id会自动放到Where条件里,其他非0字段不会
    tx := db.
        Select("uid,city,gender,keywords").       //参数也可以这样传"uid","city","gender"或者[]string{"uid","city","gender"}。没有Select时默认为select *
        Where("uid>100 and degree='大专'").         //容易发生SQL注入攻击
        Where("city in ?", []string{"北京", "上海"}). //多个Where之间是and关系
        Where("degree like ?", "%科").
        Or("gender=?", "女"). //用?占位,避免发生SQL注入攻击
        Order("id desc, uid").
        Order("city").
        Offset(3).
        Limit(1).
        First(&user) //Find可以传一个结构体,也可以传结构体切片。Take、First、Last查不到结果时会返回gorm.ErrRecordNotFound,但Find不会,Find查无结果时就不去修改结构体
    if tx.Error != nil {
        if !errors.Is(tx.Error, gorm.ErrRecordNotFound) {
            slog.Error("读DB失败", "error", tx.Error)
        } else {
            slog.Info("查无结果")
        }
    } else {
        if tx.RowsAffected > 0 {
            fmt.Printf("read结果:%+v\n", user)
        } else {
            slog.Info("查无结果", "user", user)
        }
    }

    var user2 *User //不同于var user2 User,还没申请内存空间
    // 通过反射给user2赋值时发现还没给user2申请好内存空间
    tx = db.Find(user2) //error: invalid value, should be pointer to struct or slice
    if tx.Error != nil {
        if !errors.Is(tx.Error, gorm.ErrRecordNotFound) {
            slog.Error("读DB失败", "error", tx.Error)
        } else {
            slog.Info("查无结果")
        }
    }

    var user3 *User = new(User)
    tx = db.Find(user3)
    if tx.Error != nil {
        if !errors.Is(tx.Error, gorm.ErrRecordNotFound) {
            slog.Error("读DB失败", "error", tx.Error)
        } else {
            slog.Info("查无结果")
        }
    } else {
        if tx.RowsAffected > 0 {
            fmt.Printf("read结果:%+v\n", user3)
        } else {
            slog.Info("查无结果", "user", user3)
        }
    }

    var users []User
    tx = db.Limit(3).Find(&users) //要修改切片的长度,所以要传切片的指针
    if tx.Error != nil {
        if !errors.Is(tx.Error, gorm.ErrRecordNotFound) {
            slog.Error("读DB失败", "error", tx.Error)
        } else {
            slog.Info("查无结果")
        }
    } else {
        if tx.RowsAffected > 0 {
            fmt.Println("多个read结果")
            for _, u := range users {
                fmt.Printf("%+v\n", u)
            }
        } else {
            slog.Info("查无结果")
        }
    }

    user4 := User{Id: 23212} //给主键赋值
    tx = db.Find(&user4)     //主键不为0值时暗含了一个where条件:id=47
    if tx.Error != nil {
        if !errors.Is(tx.Error, gorm.ErrRecordNotFound) {
            slog.Error("读DB失败", "error", tx.Error)
        } else {
            slog.Info("查无结果")
        }
    } else {
        if tx.RowsAffected > 0 {
            fmt.Printf("read结果:%+v\n", user4)
        } else {
            slog.Info("查无结果")
        }
    }

    // SELECT * FROM `user` USE INDEX (`id`,`idx_uid`) WHERE uid>0
    db.Where("uid>0").
        Clauses(hints.UseIndex("id", "idx_uid")). //给mysql一个建议的索引范围,这个范围之外的索引mysql就不再考虑了
        Find(&users)
    // SELECT * FROM `user` FORCE INDEX (`idx_uid`) WHERE uid>0
    db.Where("uid>0").
        Clauses(hints.ForceIndex("idx_uid")). //强制mysql使用某个索引
        Find(&users)
}

基于统计的查询

func ReadWithStatistics(db *gorm.DB) {
    type Result struct {
        City string
        Mid  float64
    }

    var results []Result
    db.Model(User{}).Select("city,avg(id) as mid").Group("city").Having("mid>0").Find(&results)
    fmt.Println("group by having查询结果:")
    for _, result := range results {
        fmt.Printf("%+v\n", result)
    }

    db.Table("user").Distinct("city").Find(&results)
    fmt.Println("distinct查询结果:")
    for _, result := range results {
        fmt.Printf("%+v\n", result)
    }

    var count int64
    db.Table("user").Where("city=?", "北京").Count(&count)
    fmt.Printf("count=%d\n", count)
}

钩子

如果任何钩子回调返回错误,GORM将停止后续的操作并回滚事务。
查询时钩子的执行时机:
// 从 db 中加载数据
// Preloading (eager loading)
AfterFind