From b0a545a6d85dd060413f6dd739eab7b75daf2e1e Mon Sep 17 00:00:00 2001 From: han-joker Date: Mon, 24 Apr 2023 20:03:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=B7=E4=BD=93=E6=93=8D=E4=BD=9C=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- context.go | 16 ++ context_test.go | 15 ++ delete.go | 45 +++++ delete_test.go | 15 ++ model.go | 4 - models.go | 83 ++++++++ operate.go | 234 ++++++++++++++++++++++ operate_test.go | 34 ++++ raw.go | 75 +++++++ raw_test.go | 15 ++ retrieve.go | 507 +++++++++++++++++++++++++++++++++++++++++++++++ retrieve_test.go | 82 ++++++++ session.go | 133 +++++++++++++ session_test.go | 19 ++ update.go | 94 +++++++++ update_test.go | 19 ++ 16 files changed, 1386 insertions(+), 4 deletions(-) create mode 100644 context.go create mode 100644 context_test.go create mode 100644 delete.go create mode 100644 delete_test.go create mode 100644 models.go create mode 100644 raw.go create mode 100644 raw_test.go create mode 100644 retrieve.go create mode 100644 retrieve_test.go create mode 100644 session.go create mode 100644 session_test.go create mode 100644 update.go create mode 100644 update_test.go diff --git a/context.go b/context.go new file mode 100644 index 0000000..256f91b --- /dev/null +++ b/context.go @@ -0,0 +1,16 @@ +package gormExample + +import ( + "context" + "fmt" + "log" +) + +func ContextTimeoutCancel(ctx context.Context) { + // 传递Context执行 + var cs []Content + if err := DB.WithContext(ctx).Limit(5).Find(&cs).Error; err != nil { + log.Fatalln(err) + } + fmt.Println(cs) +} diff --git a/context_test.go b/context_test.go new file mode 100644 index 0000000..5203a3e --- /dev/null +++ b/context_test.go @@ -0,0 +1,15 @@ +package gormExample + +import ( + "context" + "testing" + "time" +) + +func TestContextTimeoutCancel(t *testing.T) { + // 设置一个定时Cancel的Context + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) + defer cancel() + + ContextTimeoutCancel(ctx) +} diff --git a/delete.go b/delete.go new file mode 100644 index 0000000..7002f1f --- /dev/null +++ b/delete.go @@ -0,0 +1,45 @@ +package gormExample + +import ( + "fmt" + "log" +) + +func DeleteWhere() { + + result1 := DB.Delete(&Content{}, "likes < ?", 100) + if result1.Error != nil { + log.Fatalln(result1.Error) + } + //[9.643ms] [rows:0] UPDATE `msb_content` SET `deleted_at`='2023-04-21 19:11:47.799' WHERE likes < 100 AND `msb_content`.`deleted_at` IS NULL + + result2 := DB.Where("likes < ?", 100).Delete(&Content{}) + if result2.Error != nil { + log.Fatalln(result2.Error) + } + //[5.928ms] [rows:0] UPDATE `msb_content` SET `deleted_at`='2023-04-21 19:11:47.807' WHERE likes < 100 AND `msb_content`.`deleted_at` IS NULL +} + +func FindDeleted() { + var c Content + DB.Delete(&c, 13) + + if err := DB.First(&c, 13).Error; err != nil { + log.Println(err) + } + //[4.604ms] [rows:0] SELECT * FROM `msb_content` WHERE `msb_content`.`id` = 13 AND `msb_content`.`deleted_at` IS NULL ORDER BY `msb_content`.`id` LIMIT 1 + + if err := DB.Unscoped().First(&c, 13).Error; err != nil { + log.Println(err) + } + // [3.320ms] [rows:1] SELECT * FROM `msb_content` WHERE `msb_content`.`id` = 13 ORDER BY `msb_content`.`id` LIMIT 1 + fmt.Printf("%+v\n", c) +} + +func DeleteHard() { + var c Content + if err := DB.Unscoped().Delete(&c, 14).Error; err != nil { + log.Fatalln(err) + } + // [8.135ms] [rows:0] DELETE FROM `msb_content` WHERE `msb_content`.`id` = 14 +} diff --git a/delete_test.go b/delete_test.go new file mode 100644 index 0000000..04f64ac --- /dev/null +++ b/delete_test.go @@ -0,0 +1,15 @@ +package gormExample + +import "testing" + +func TestDeleteWhere(t *testing.T) { + DeleteWhere() +} + +func TestFindDeleted(t *testing.T) { + FindDeleted() +} + +func TestDeleteHard(t *testing.T) { + DeleteHard() +} diff --git a/model.go b/model.go index b913dc6..b1681d5 100644 --- a/model.go +++ b/model.go @@ -304,7 +304,3 @@ type BlogBasic struct { Summary string Content string } -type Author struct { - Name string - Emaile string -} diff --git a/models.go b/models.go new file mode 100644 index 0000000..befa51f --- /dev/null +++ b/models.go @@ -0,0 +1,83 @@ +package gormExample + +import ( + "gorm.io/gorm" + "gorm.io/gorm/clause" + "log" + "time" +) + +type Content struct { + gorm.Model + + Subject string + Likes uint `gorm:""` + Views uint `gorm:""` + //Likes uint `gorm:"default:99"` + //Views *uint `gorm:"default:99"` + PublishTime *time.Time + // 不需要迁移 + // 禁用写操作 + Sv string `gorm:"-:migration;<-:false"` + + // 作者ID + AuthorID uint +} + +// Author模型 +type Author struct { + gorm.Model + Status int + + Name string + Email string +} + +type ContentStrPK struct { + ID string `gorm:"primaryKey"` + Subject string + Likes uint + Views uint + PublishTime *time.Time +} + +const ( + defaultViews = 99 + defaultLikes = 99 +) + +func NewContent() Content { + return Content{ + Likes: defaultLikes, + Views: defaultViews, + } +} + +// Hook +func (c *Content) BeforeCreate(db *gorm.DB) error { + // 业务 + if c.PublishTime == nil { + now := time.Now() + c.PublishTime = &now + } + + // 配置 + db.Statement.AddClause(clause.OnConflict{UpdateAll: true}) + + log.Println("content before create hook") + + return nil +} + +func (c *Content) AfterCreate(db *gorm.DB) error { + //return errors.New("custom error") + return nil +} + +func (c *Content) AfterFind(db *gorm.DB) error { + if c.AuthorID == 0 { + c.AuthorID = 1 // 1, 假定的默认作者 + } + + return nil +} diff --git a/operate.go b/operate.go index b44108a..f53601a 100644 --- a/operate.go +++ b/operate.go @@ -1 +1,235 @@ package gormExample + +import ( + "fmt" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "log" + "time" +) + +type User struct { + gorm.Model + + Username string + Name string + Email string + Birthday *time.Time +} + +func OperatorType() { + DB.AutoMigrate(&User{}) + + var users []User + + // 一步操作 + err := DB. + Order("name DESC"). + Where("email like ?", "@163.com%"). + Where("birthday IS NOT NULL"). + Find(&users).Error // select all + if err != nil { + log.Fatal(err) + } + + // 分步操作 + query := DB.Where("birthday IS NOT NULL") + query = query.Where("email like ?", "@163.com%") + query = query.Order("name DESC") + query.Find(&users) +} + +func CreateBasic() { + DB.AutoMigrate(&Content{}) + + // 模型映射记录,操作模型字段,就是操作记录的列 + c1 := Content{} + c1.Subject = "GORM的使用" + + // 执行创建(insert) + result1 := DB.Create(&c1) + // 处理错误 + if result1.Error != nil { + log.Fatal(result1.Error) + } + // 最新的ID,和影响的记录数 + fmt.Println(c1.ID, result1.RowsAffected) + + // map 指定数据 + //设置map 的values + values := map[string]any{ + "Subject": "Map指定值", + "PublishTime": time.Now(), + } + // create + result2 := DB.Model(&Content{}).Create(values) + if result2.Error != nil { + log.Fatal(result2.Error) + } + // 测试输出 + fmt.Println(result2.RowsAffected) +} + +func CreateMulti() { + DB.AutoMigrate(&Content{}) + + // 定义模型的切片 + models := []Content{ + {Subject: "标题1"}, + {Subject: "标题2"}, + {Subject: "标题3"}, + } + // create 插入 + result := DB.Create(&models) + if result.Error != nil { + log.Fatal(result.Error) + } + fmt.Println("RowsAffected:", result.RowsAffected) + for _, m := range models { + fmt.Println("ID:", m.ID) + } + + // 切片结构同样支持 + vs := []map[string]any{ + {"Subject": "标题4"}, + {"Subject": "标题5"}, + {"Subject": "标题6"}, + } + result2 := DB.Model(&Content{}).Create(vs) + if result2.Error != nil { + log.Fatal(result2.Error) + } + fmt.Println("RowsAffected:", result2.RowsAffected) + +} + +func CreateBatch() { + DB.AutoMigrate(&Content{}) + + // 定义模型的切片 + models := []Content{ + {Subject: "标题1"}, + {Subject: "标题2"}, + {Subject: "标题3"}, + } + // create 插入 + result := DB.CreateInBatches(&models, 2) + if result.Error != nil { + log.Fatal(result.Error) + } + fmt.Println("RowsAffected:", result.RowsAffected) + for _, m := range models { + fmt.Println("ID:", m.ID) + } + + // 切片结构同样支持 + vs := []map[string]any{ + {"Subject": "标题4"}, + {"Subject": "标题5"}, + {"Subject": "标题6"}, + } + result2 := DB.Model(&Content{}).CreateInBatches(vs, 2) + if result2.Error != nil { + log.Fatal(result2.Error) + } + fmt.Println("RowsAffected:", result2.RowsAffected) +} + +func UpSert() { + DB.AutoMigrate(&Content{}) + + // 常规插入,原始数据 + c1 := Content{} + c1.Subject = "原始标题" + c1.Likes = 10 + DB.Create(&c1) + fmt.Println(c1) + + // 主键冲突的错误 + //c2 := Content{} + //c2.ID = c1.ID + //c2.Subject = "新标题" + //c2.Likes = 20 + //if err := DB.Create(&c2).Error; err != nil { + // log.Fatal(err) + // // Error 1062 (23000): Duplicate entry '17' for key 'msb_content.PRIMARY' + //} + + // 冲突后,更新全部字段 + //c3 := Content{} + //c3.ID = c1.ID + //c3.Subject = "新标题" + //c3.Likes = 20 + //if err := DB. + // Clauses(clause.OnConflict{ + // UpdateAll: true, + // }). + // Create(&c3).Error; err != nil { + // log.Fatal(err) + //} + + // 冲突后,更新部分字段 + c4 := Content{} + c4.ID = c1.ID + c4.Subject = "新标题" + c4.Likes = 20 + if err := DB. + Clauses(clause.OnConflict{ + DoUpdates: clause.AssignmentColumns([]string{"likes"}), + }). + Create(&c4).Error; err != nil { + log.Fatal(err) + } + +} + +func DefaultValue() { + DB.AutoMigrate(&Content{}) + + // 常规插入,原始数据 + c1 := Content{} + c1.Subject = "原始标题" + c1.Likes = 0 + //views := uint(0) + //c1.Views = &views + DB.Create(&c1) + //fmt.Println(c1.Likes, *c1.Views) +} + +func DefaultValueOften() { + DB.AutoMigrate(&Content{}) + + c1 := NewContent() + c1.Subject = "原始标题" + DB.Create(&c1) + fmt.Println(c1.Likes, c1.Views) +} + +func SelectOmit() { + DB.AutoMigrate(&Content{}) + + c1 := Content{} + c1.Subject = "原始标题" + c1.Likes = 10 + c1.Views = 99 + now := time.Now() + c1.PublishTime = &now + + //DB.Select("Subject", "Likes", "UpdatedAt").Create(&c1) + //INSERT INTO `msb_content` (`created_at`,`updated_at`,`subject`,`likes`) VALUES ('2023-04-11 18:11:26.384','2023-04-11 18:11:26.384','原始标题',10) + + DB.Omit("Subject", "Likes", "UpdatedAt").Create(&c1) + //INSERT INTO `msb_content` (`created_at`,`deleted_at`,`views`,`publish_time`) VALUES ('2023-04-11 18:12:39.431',NULL,99,'2023-04-11 18:12:39.429') +} + +func CreateHook() { + DB.AutoMigrate(&Content{}) + c1 := Content{} + + err := DB.Create(&c1).Error + if err != nil { + log.Fatal(err) + } + //INSERT INTO `msb_content` (`created_at`,`updated_at`,`deleted_at`,`subject`,`likes`,`views`,`publish_time`) VALUES ('2023-04-11 18:44:56.62','2023-04-11 18:44:56.62',NULL,'',0,0,'2023-04-11 18:44:56.62') ON DUPLICATE KEY UPDATE `updated_at`='2023-04-11 18:44:56.62',`deleted_at`=VALUES(`deleted_at`),`subject`=VALUES(`subject`),`likes`=VALUES(`likes`),`views`=VALUES(`views`),`publish_time`=VALUES(`publish_time`) + +} diff --git a/operate_test.go b/operate_test.go index b44108a..09596b9 100644 --- a/operate_test.go +++ b/operate_test.go @@ -1 +1,35 @@ package gormExample + +import "testing" + +func TestOperatorType(t *testing.T) { + OperatorType() +} + +func TestCreateBasic(t *testing.T) { + CreateBasic() +} + +func TestCreateMulti(t *testing.T) { + CreateMulti() +} + +func TestCreateBatch(t *testing.T) { + CreateBatch() +} + +func TestUpSert(t *testing.T) { + UpSert() +} + +func TestDefaultValue(t *testing.T) { + DefaultValue() +} + +func TestSelectOmit(t *testing.T) { + SelectOmit() +} + +func TestCreateHook(t *testing.T) { + CreateHook() +} diff --git a/raw.go b/raw.go new file mode 100644 index 0000000..e6aee5e --- /dev/null +++ b/raw.go @@ -0,0 +1,75 @@ +package gormExample + +import "log" + +// 原生查询测试 +func RawSelect() { + // 结果类型 + type Result struct { + ID uint + Subject string + Likes, Views int + } + var rs []Result + + // SQL + sql := "SELECT `id`, `subject`, `likes`, `views` FROM `msb_content` WHERE `likes` > ? ORDER BY `likes` DESC LIMIT ?" + + // 执行SQL,并扫描结果 + if err := DB.Raw(sql, 99, 12).Scan(&rs).Error; err != nil { + log.Fatalln(err) + } + // [8.298ms] [rows:12] SELECT `id`, `subject`, `likes`, `views` FROM `msb_content` WHERE `likes` > 99 ORDER BY `likes` DESC LIMIT 12 + + log.Println(rs) +} + +// 执行类的SQL原生 +func RawExec() { + // SQL + sql := "UPDATE `msb_content` SET `subject` = CONCAT(`subject`, '-new postfix') WHERE `id` BETWEEN ? AND ?" + + // 执行,获取结果 + result := DB.Exec(sql, 30, 40) + if result.Error != nil { + log.Fatalln(result.Error) + } + // [13.369ms] [rows:10] UPDATE `msb_content` SET `subject` = CONCAT(`subject`, '-new postfix') WHERE `id` BETWEEN 30 AND 40 + + log.Println(result.RowsAffected) + +} + +// sql.Row 或 sql.Rows 类型的结果处理 +func RowAndRows() { + // sql + sql := "SELECT `id`, `subject`, `likes`, `views` FROM `msb_content` WHERE `likes` > ? ORDER BY `likes` DESC LIMIT ?" + + // 执行,获取rows + rows, err := DB.Raw(sql, 99, 12).Rows() + if err != nil { + log.Fatalln(err) + } + + // 遍历rows + for rows.Next() { + // 扫描的列独立的变量 + //var id uint + //var subject string + //var likes, views int + //rows.Scan(&id, &subject, &likes, &views) + //log.Println(id, subject, likes, views) + + // 扫描到整体结构体 + type Result struct { + ID uint + Subject string + Likes, Views int + } + var r Result + if err := DB.ScanRows(rows, &r); err != nil { + log.Fatalln(err) + } + log.Println(r) + } +} diff --git a/raw_test.go b/raw_test.go new file mode 100644 index 0000000..28e52b1 --- /dev/null +++ b/raw_test.go @@ -0,0 +1,15 @@ +package gormExample + +import "testing" + +func TestRawSelect(t *testing.T) { + RawSelect() +} + +func TestRawExec(t *testing.T) { + RawExec() +} + +func TestRowAndRows(t *testing.T) { + RowAndRows() +} diff --git a/retrieve.go b/retrieve.go new file mode 100644 index 0000000..0a416e2 --- /dev/null +++ b/retrieve.go @@ -0,0 +1,507 @@ +package gormExample + +import ( + "database/sql" + "fmt" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "log" + "time" +) + +func GetByPk() { + // migrate + DB.AutoMigrate(&Content{}, &ContentStrPK{}) + + // 查询单条 + ////c := Content{} + ////if err := DB.First(&c, 10).Error; err != nil { + //// log.Println(err) + ////} + //c := Content{} + //c.ID = 10 + //if err := DB.First(&c).Error; err != nil { + // log.Println(err) + //} + //// 字符串类型的主键 + //cStr := ContentStrPK{} + //if err := DB.First(&cStr, "id = ?", "some pk").Error; err != nil { + // //if err := DB.First(&cStr, "some pk").Error; err != nil { + // log.Println(err) + //} + // + //// 查询多条 + //var cs []Content + //if err := DB.Find(&cs, []uint{10, 11, 12}).Error; err != nil { + // log.Println(err) + //} + // 字符串类型的主键 + var cStrs []ContentStrPK + if err := DB.Find(&cStrs, []string{"some", "pk", "item"}).Error; err != nil { + log.Println(err) + } +} + +func GetOne() { + c := Content{} + if err := DB.First(&c, "id > ?", 42).Error; err != nil { + log.Println(err) + } + + o := Content{} + if err := DB.Last(&o, "id > ?", 42).Error; err != nil { + log.Println(err) + } + + n := Content{} + if err := DB.Take(&n, "id > ?", 42).Error; err != nil { + log.Println(err) + } + + f := Content{} + if err := DB.Limit(1).Find(&f, "id > ?", 42).Error; err != nil { + log.Println(err) + } + + fs := Content{} + if err := DB.Find(&fs, "id > ?", 42).Error; err != nil { + log.Println(err) + } +} + +func GetToMap() { + // 单条 + c := map[string]any{} //map[string]interface{}{} + if err := DB.Model(&Content{}).First(&c, 13).Error; err != nil { + log.Println(err) + } + //fmt.Println(c["id"], c["id"].(uint) == 13) + // 需要接口类型断言,才能继续处理 + if c["id"].(uint) == 13 { + fmt.Println("id bingo") + } + // time类型的处理 + fmt.Println(c["created_at"]) + t, err := time.Parse("2006-01-02 15:04:05.000 -0700 CST", "2023-04-10 22:00:11.582 +0800 CST") + if err != nil { + log.Println(err) + } + if c["created_at"].(time.Time) == t { + fmt.Println("created_at bingo") + } + + // 多条 + var cs []map[string]any + if err := DB.Model(&Content{}).Find(&cs, []uint{13, 14, 15}).Error; err != nil { + log.Println(err) + } + for _, c := range cs { + fmt.Println(c["id"].(uint), c["subject"].(string), c["created_at"].(time.Time)) + } +} + +func GetPluck() { + // 使用切片存储 + var subjects []sql.NullString + //var subjects []string + if err := DB.Model(&Content{}).Pluck("subject", &subjects).Error; err != nil { + log.Println(err) + } + + for _, subject := range subjects { + //NullString的使用 + if subject.Valid { + fmt.Println(subject.String) + } else { + fmt.Println("[NULL]") + } + + // + //fmt.Println(subject) + } + +} + +func GetPluckExp() { + // 使用切片存储,如果表达式可以保证NULL不会出现了,就可以不适用NullType了 + var subjects []string + // 字段为表达式的结果 + if err := DB.Model(&Content{}).Pluck("concat(coalesce(subject, '[no subject]'), '-', likes)", &subjects).Error; err != nil { + log.Println(err) + } + + for _, subject := range subjects { + fmt.Println(subject) + } +} + +func GetSelect() { + var c Content + + // 基本的字段名 + //if err := DB.Select("subject", "likes").First(&c, 13).Error; err != nil { + // log.Fatalln(err) + //} + + // 字段表达式 + if err := DB.Select("subject", "likes", "concat(subject,'-', views) AS sv").First(&c, 13).Error; err != nil { + log.Fatalln(err) + } + + fmt.Printf("%+v\n", c) +} + +func GetDistinct() { + var cs []Content + + // 基本的字段名 + if err := DB.Distinct("*").Find(&cs).Error; err != nil { + log.Fatalln(err) + } + + fmt.Printf("%+v\n", cs) + +} + +func WhereMethod() { + var cs []Content + + // inline条件,内联条件 + //if err := DB.Find(&cs, "likes > ? AND subject like ?", 100, "gorm%").Error; err != nil { + // log.Fatalln(err) + //} + // SELECT * FROM `msb_content` WHERE (likes > 100 AND subject like 'gorm%') AND `msb_content`.`deleted_at` IS NULL + + // Where,通常在动态拼凑条件时使用 + //query := DB.Where("likes > ?", 100) + //subject := "" + //// 当前用户输出subject,不为空字符串时,才拼凑subject条件 + //if subject != "" { + // query.Where("subject like ?", subject+"%") + //} + //if err := query.Find(&cs).Error; err != nil { + // log.Fatalln(err) + //} + // SELECT * FROM `msb_content` WHERE likes > 100 AND `msb_content`.`deleted_at` IS NULL + + // OR 逻辑运算 + //query := DB.Where("likes > ?", 100) + //subject := "gorm" + //// 当前用户输出subject,不为空字符串时,才拼凑subject条件 + //if subject != "" { + // //query.Where("subject like ?", "subject"+"%") + // query.Or("subject like ?", subject+"%") + //} + //if err := query.Find(&cs).Error; err != nil { + // log.Fatalln(err) + //} + // SELECT * FROM `msb_content` WHERE (likes > 100 OR subject like 'gorm%') AND `msb_content`.`deleted_at` IS NULL + + // Not 逻辑运算 + query := DB.Where("likes > ?", 100) + subject := "gorm" + // 当前用户输出subject,不为空字符串时,才拼凑subject条件 + if subject != "" { + //query.Not("subject like ?", subject+"%") + // SELECT * FROM `msb_content` WHERE likes > 100 AND NOT subject like 'gorm%' AND `msb_content`.`deleted_at` IS NULL + + query = query.Or(DB.Not("subject like ?", subject+"%")) + // SELECT * FROM `msb_content` WHERE (likes > 100 OR NOT subject like 'gorm%') AND `msb_content`.`deleted_at` IS NULL + + } + if err := query.Find(&cs).Error; err != nil { + log.Fatalln(err) + } + +} + +func WhereType() { + var cs []Content + + // 嵌套分组构建复杂的逻辑运算 + // (1 or 2) and (3 and (4 or 5)) + // 1 or 2 + //condA := DB.Where("likes > ?", 10).Or("likes <= ?", 100) + // 3 and (4 or 5) + //condB := DB.Where("views > ?", 20).Where(DB.Where("views <= ?", 200).Or("subject like ?", "gorm%")) + //query := DB.Where(condA).Where(condB) + // SELECT * FROM `msb_content` WHERE (likes > 10 OR likes <= 100) AND (views > 20 AND (views <= 200 OR subject like 'gorm%')) AND `msb_content`.`deleted_at` IS NULL + + // map构建条件, and, = in + //query := DB.Where(map[string]any{ + // "views": 100, + // "id": []uint{1, 2, 3, 4, 5}, + //}) + // SELECT * FROM `msb_content` WHERE `id` IN (1,2,3,4,5) AND `views` = 100 AND `msb_content`.`deleted_at` IS NULL + + // struct条件构建, and, = + query := DB.Where(Content{ + Views: 100, + Subject: "GORM", + }) + // SELECT * FROM `msb_content` WHERE `msb_content`.`subject` = 'GORM' AND `msb_content`.`views` = 100 AND `msb_content`.`deleted_at` IS NULL + + if err := query.Find(&cs).Error; err != nil { + log.Fatalln(err) + } +} + +func PlaceHolder() { + var cs []Content + + // 匿名 + //query := DB.Where("likes = ? AND subject like ?", 100, "gorm%") + + // 具名,绑定名字sql.Named()结构 + //query := DB.Where("likes = @like AND subject like @subject", sql.Named("subject", "gorm%"), sql.Named("like", 100)) + // SELECT * FROM `msb_content` WHERE (likes = 100 AND subject like 'gorm%') AND `msb_content`.`deleted_at` IS NULL + + // gorm还支持使用map的形式具名绑定 + query := DB.Where("likes = @like AND subject like @subject", map[string]any{ + "subject": "gorm%", + "like": 100, + }) + // SELECT * FROM `msb_content` WHERE (likes = 100 AND subject like 'gorm%') AND `msb_content`.`deleted_at` IS NULL + + if err := query.Find(&cs).Error; err != nil { + log.Fatalln(err) + } +} + +func OrderBy() { + var cs []Content + + ids := []uint{2, 3, 1} + //query := DB.Order("FIELD(id, 2, 3, 1)") + + query := DB.Clauses(clause.OrderBy{ + Expression: clause.Expr{ + SQL: "FIELD(id, ?)", + Vars: []any{ids}, + WithoutParentheses: true, + }, + }) + // SELECT * FROM `msb_content` WHERE `msb_content`.`id` IN (2,3,1) AND `msb_content`.`deleted_at` IS NULL ORDER BY FIELD(id, 2,3,1) + + if err := query.Find(&cs, ids).Error; err != nil { + log.Fatalln(err) + } + + for _, c := range cs { + fmt.Println(c.ID) + } +} + +// 定义分页必要数据结构 +type Pager struct { + Page, PageSize int +} + +// 默认的值 +const ( + DefaultPage = 1 + DefaultPageSize = 12 +) + +// 翻页程序 +func Pagination(pager Pager) { + // 确定page, offset 和 pagesize + + page := DefaultPage + if pager.Page != 0 { + page = pager.Page + } + + pagesize := DefaultPageSize + if pager.PageSize != 0 { + pagesize = pager.PageSize + } + + // 计算offset + // page, pagesize, offset + // 1, 10, 0 + // 2, 10, 10 + // 3, 10, 20 + offset := pagesize * (page - 1) + + var cs []Content + // SELECT * FROM `msb_content` WHERE `msb_content`.`deleted_at` IS NULL LIMIT 15 OFFSET 30 + if err := DB.Offset(offset).Limit(pagesize).Find(&cs).Error; err != nil { + log.Fatalln(err) + } +} + +// 用于得到func(db *gorm.DB) *gorm.DB类型函数 +// 为什么不直接定义函数,因为需要func(db *gorm.DB) *gorm.DB与分页信息产生联系。 +func Paginate(pager Pager) func(db *gorm.DB) *gorm.DB { + // 计算page + page := DefaultPage + if pager.Page != 0 { + page = pager.Page + } + + // 计算pagesize + pagesize := DefaultPageSize + if pager.PageSize != 0 { + pagesize = pager.PageSize + } + + // 计算offset + // page, pagesize, offset + // 1, 10, 0 + // 2, 10, 10 + // 3, 10, 20 + offset := pagesize * (page - 1) + + return func(db *gorm.DB) *gorm.DB { + // 使用闭包的变量,实现翻页的业务逻辑 + return db.Offset(offset).Limit(pagesize) + } +} + +// 测试重用的分页查询 +func PaginationScope(pager Pager) { + + var cs []Content + // SELECT * FROM `msb_content` WHERE `msb_content`.`deleted_at` IS NULL LIMIT 15 OFFSET 30 + if err := DB.Scopes(Paginate(pager)).Find(&cs).Error; err != nil { + log.Fatalln(err) + } + + var ps []Post + // SELECT * FROM `msb_post` WHERE `msb_content`.`deleted_at` IS NULL LIMIT 15 OFFSET 30 + if err := DB.Scopes(Paginate(pager)).Find(&ps).Error; err != nil { + log.Fatalln(err) + } +} + +func Count(pager Pager) { + + // 集中的条件,用于统计数量和获取某页记录 + query := DB.Model(&Content{}). + Where("likes > ?", 99) + + // total rows count + var count int64 + if err := query.Count(&count).Error; err != nil { + log.Fatalln(err) + } + // SELECT count(*) FROM `msb_content` WHERE likes > 99 AND `msb_content`.`deleted_at` IS NULL + // 计算总页数 ceil( count / pagesize) + + // rows per page + var cs []Content + if err := query.Scopes(Paginate(pager)).Find(&cs).Error; err != nil { + log.Fatalln(err) + } + // SELECT * FROM `msb_content` WHERE likes > 99 AND `msb_content`.`deleted_at` IS NULL LIMIT 15 OFFSET 30 +} + +func GroupHaving() { + DB.AutoMigrate(&Content{}) + + // 定义查询结构类型 + type Result struct { + // 分组字段 + AuthorID uint + + // 合计字段 + TotalViews int + TotalLikes int + AvgViews float64 + } + + // 执行分组合计过滤查询 + var rs []Result + if err := DB.Model(&Content{}). + Select("author_id", "SUM(views) as total_views", "SUM(likes) as total_likes", "AVG(views) as avg_views"). + Group("author_id").Having("total_views > ?", 99). + Find(&rs).Error; err != nil { + log.Fatalln(err) + } + // SQL + // SELECT `author_id`,SUM(views) as total_views,SUM(likes) as total_likes,AVG(views) as avg_views FROM `msb_content` WHERE `msb_content`.`deleted_at` IS NULL GROUP BY `author_id` HAVING total_views > 99 + +} + +func Iterator() { + // 利用DB.Rows() 获取Rows对象 + rows, err := DB.Model(&Content{}).Rows() + if err != nil { + log.Fatalln(err) + } + // [rows:-] SELECT * FROM `msb_content` WHERE `msb_content`.`deleted_at` IS NULL + + // 注意:保证使用过后关闭rows结果集 + defer func() { + _ = rows.Close() + }() + fmt.Println(rows) + + // 迭代的从Rows中扫描记录到模型 + for rows.Next() { + // 还有记录存在与结果集中 + var c Content + if err := DB.ScanRows(rows, &c); err != nil { + log.Fatalln(err) + } + fmt.Println(c.Subject) + } +} + +func Locking() { + var cs []Content + + if err := DB. + Clauses(clause.Locking{Strength: "UPDATE"}). + Find(&cs).Error; err != nil { + log.Fatalln(err) + } + // [4.998ms] [rows:42] SELECT * FROM `msb_content` WHERE `msb_content`.`deleted_at` IS NULL FOR UPDATE + + if err := DB. + Clauses(clause.Locking{Strength: "SHARE"}). + Find(&cs).Error; err != nil { + log.Fatalln(err) + } + // [3.006ms] [rows:42] SELECT * FROM `msb_content` WHERE `msb_content`.`deleted_at` IS NULL FOR SHARE +} + +func SubQuery() { + // migrate + DB.AutoMigrate(&Author{}, &Content{}) + + // 条件型子查询 + //select * from content where author_id in (select id from author where status=0); + // 子查询,不需要使用终结方法Find完成查询,只需要构建语句即可 + whereSubQuery := DB.Model(&Author{}).Select("id").Where("status = ?", 0) + var cs []Content + if err := DB.Where("author_id IN (?)", whereSubQuery).Find(&cs).Error; err != nil { + log.Fatalln(err) + } + // [4.782ms] [rows:0] SELECT * FROM `msb_content` WHERE author_id IN (SELECT `id` FROM `msb_author` WHERE status = 0 AND `msb_author`.`deleted_at` IS NULL) AND `msb_content`.`deleted_at` IS NULL + + // from型子查询 + //select * from (select subject, likes from content where publish_time is null) as temp where likes > 10; + fromSubQuery := DB.Model(&Content{}).Where("publish_time IS NULL").Select("subject", "likes") + type Result struct { + Subject string + Likes int + } + var rs []Result + if err := DB.Table("(?) AS temp", fromSubQuery). + Where("likes > ?", 10). + Find(&rs).Error; err != nil { + log.Fatalln(err) + } + // [3.800ms] [rows:17] SELECT * FROM (SELECT `subject`,`likes` FROM `msb_content` WHERE publish_time IS NULL AND `msb_content`.`deleted_at` IS NULL) AS temp WHERE likes > 10 +} + +func FindHook() { + var c Content + if err := DB.First(&c, 13).Error; err != nil { + log.Fatalln(err) + } + + fmt.Printf("%+v\n", c) +} diff --git a/retrieve_test.go b/retrieve_test.go new file mode 100644 index 0000000..daacc9b --- /dev/null +++ b/retrieve_test.go @@ -0,0 +1,82 @@ +package gormExample + +import "testing" + +func TestGetByPk(t *testing.T) { + GetByPk() +} + +func TestGetOne(t *testing.T) { + GetOne() +} + +func TestGetToMap(t *testing.T) { + GetToMap() +} + +func TestGetPluck(t *testing.T) { + GetPluck() +} + +func TestGetPluckExp(t *testing.T) { + GetPluckExp() +} + +func TestGetSelect(t *testing.T) { + GetSelect() +} + +func TestGetDistinct(t *testing.T) { + GetDistinct() +} + +func TestWhereMethod(t *testing.T) { + WhereMethod() +} + +func TestWhereType(t *testing.T) { + WhereType() +} + +func TestPlaceHolder(t *testing.T) { + PlaceHolder() +} + +func TestOrderBy(t *testing.T) { + OrderBy() +} + +func TestPagination(t *testing.T) { + request := Pager{3, 15} + Pagination(request) // offset:30, limit:15 +} + +func TestPaginationScope(t *testing.T) { + request := Pager{3, 15} + PaginationScope(request) +} + +func TestCount(t *testing.T) { + request := Pager{3, 15} + Count(request) +} + +func TestGroupHaving(t *testing.T) { + GroupHaving() +} + +func TestIterator(t *testing.T) { + Iterator() +} + +func TestLocking(t *testing.T) { + Locking() +} + +func TestSubQuery(t *testing.T) { + SubQuery() +} + +func TestFindHook(t *testing.T) { + FindHook() +} diff --git a/session.go b/session.go new file mode 100644 index 0000000..15f506d --- /dev/null +++ b/session.go @@ -0,0 +1,133 @@ +package gormExample + +import ( + "fmt" + "gorm.io/gorm" + "log" +) + +func SessionIssue() { + // 连续控制条件 + //db := DB.Model(&Content{}).Where("views > ?", 100) + //db.Where("likes > ?", 9) + //var cs []Content + //db.Find(&cs) + // [4.319ms] [rows:0] SELECT * FROM `msb_content` WHERE views > 100 AND likes > 9 AND `msb_content`.`deleted_at` IS NULL + + // 连续执行查询 + // 1 + // Where("views > ?", 100).Where("likes > ?", 9) + db := DB.Model(&Content{}).Where("views > ?", 100) + + db.Where("likes > ?", 9) + var cs1 []Content + db.Find(&cs1) + // [10.566ms] [rows:0] SELECT * FROM `msb_content` WHERE views > 100 AND likes > 9 AND `msb_content`.`deleted_at` IS NULL + + // 2,找到likes<5 + // Where("views > ?", 100).Where("likes < ?", 5) + db.Where("likes < ?", 5) + var cs2 []Content + db.Find(&cs2) + // [2.815ms] [rows:0] SELECT * FROM `msb_content` WHERE views > 100 AND likes > 9 AND `msb_content`.`deleted_at` IS NULL AND likes < 5 +} + +func SessionDB() { + // 连续执行查询 + // 1 + // Where("views > ?", 100).Where("likes > ?", 9) + db1 := DB.Model(&Content{}).Where("views > ?", 100) + db1.Where("likes > ?", 9) + var cs1 []Content + db1.Find(&cs1) + // [10.683ms] [rows:0] SELECT * FROM `msb_content` WHERE views > 100 AND likes > 9 AND `msb_content`.`deleted_at` IS NULL + + // 2,找到likes<5 + // Where("views > ?", 100).Where("likes < ?", 5) + db2 := DB.Model(&Content{}).Where("views > ?", 100) + db2.Where("likes < ?", 5) + var cs2 []Content + db2.Find(&cs2) + // [4.139ms] [rows:0] SELECT * FROM `msb_content` WHERE views > 100 AND likes < 5 AND `msb_content`.`deleted_at` IS NULL +} + +func SessionNew() { + + // 需要重复使用的部分 + // 将Session方法前的配置,记录到了当前的会话中 + // 后边再次调用db的方法直到终结方法,会保持会话中的子句选项 + // 执行完终结方法后,再次调用db的方法到终结方法,可以重用会话中的子句选项。 + db := DB.Model(&Content{}).Where("views > ?", 100).Session(&gorm.Session{}) + + // 连续执行查询 + // 1 + // Where("views > ?", 100).Where("likes > ?", 9) + var cs1 []Content + db.Where("likes > ?", 9).Find(&cs1) + // [4.633ms] [rows:0] SELECT * FROM `msb_content` WHERE views > 100 AND likes > 9 AND `msb_content`.`deleted_at` IS NULL + + // 2,找到likes<5 + // Where("views > ?", 100).Where("likes < ?", 5) + var cs2 []Content + db.Where("likes < ?", 5).Find(&cs2) + // [3.846ms] [rows:0] SELECT * FROM `msb_content` WHERE views > 100 AND likes < 5 AND `msb_content`.`deleted_at` IS NULL +} + +func SessionOptions() { + // Skip Hook + //db := DB.Session(&gorm.Session{ + // SkipHooks: true, + //}) + //db.Save(&Content{Subject: "no create hook"}) + + // DryRun + //db := DB.Session(&gorm.Session{ + // DryRun: true, + //}) + //stmt := db.Save(&Content{}).Statement + //fmt.Println(stmt.SQL.String()) + //// INSERT INTO `msb_content` (`created_at`,`updated_at`,`deleted_at`,`subject`,`likes`,`views`,`publish_time`,`author_id`) VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE `updated_at`=?,`deleted_at`=VALUES(`deleted_at`),`subject`=VALUES(`subject`),`likes`=VALUES(`likes`),`views`=VALUES(`views`),`publish_time`=VALUES(`publish_time`),`author_id`=VALUES(`author_id`) + //fmt.Println(stmt.Vars) + //// [2023-04-24 17:25:47.827 +0800 CST 2023-04-24 17:25:47.827 +0800 CST {0001-01-01 00:00:00 +0000 UTC false} 0 0 2023-04-24 17:25:47.823908 +0800 CST 0 2023-04-24 17:25:47.827 +0800 CST] + + // Debug + //DB.Debug().First() + + // prepare 预编译,编译重用,编译独立 + // SQL, 编译,绑定数据,执行 + // 预编译,将编译的过程缓存起来,便于重用。 + // 在执行结构相同的SQL时,可以重用。 + db := DB.Session(&gorm.Session{ + PrepareStmt: true, + }) + + // 预编译管理对象 + stmtManager, ok := db.ConnPool.(*gorm.PreparedStmtDB) + if !ok { + log.Fatalln("*gorm.PreparedStmtDB assert failed") + } + fmt.Println(stmtManager.PreparedSQL) + fmt.Println(stmtManager.Stmts) + + var c1 Content + db.First(&c1, 13) + fmt.Println(stmtManager.PreparedSQL) + fmt.Println(stmtManager.Stmts) + // [SELECT * FROM `msb_content` WHERE `msb_content`.`id` = ? AND `msb_content`.`deleted_at` IS NULL ORDER BY `msb_content`.`id` LIMIT 1] + // map[SELECT * FROM `msb_content` WHERE `msb_content`.`id` = ? AND `msb_content`.`deleted_at` IS NULL ORDER BY `msb_content`.`id` LIMIT 1:0xc0001cd920] + + var c2 Content + db.First(&c2, 14) + fmt.Println(stmtManager.PreparedSQL) + fmt.Println(stmtManager.Stmts) + // [SELECT * FROM `msb_content` WHERE `msb_content`.`id` = ? AND `msb_content`.`deleted_at` IS NULL ORDER BY `msb_content`.`id` LIMIT 1] + // map[SELECT * FROM `msb_content` WHERE `msb_content`.`id` = ? AND `msb_content`.`deleted_at` IS NULL ORDER BY `msb_content`.`id` LIMIT 1:0xc0001cd920] + + var c3 []Content + db.Find(&c3, []uint{15, 16}) + fmt.Println(stmtManager.PreparedSQL) + fmt.Println(stmtManager.Stmts) + // [SELECT * FROM `msb_content` WHERE `msb_content`.`id` = ? AND `msb_content`.`deleted_at` IS NULL ORDER BY `msb_content`.`id` LIMIT 1 SELECT * FROM `msb_content` WHERE `msb_content`.`id` IN (?,?) AND `msb_content`.`deleted_at` IS NULL] + // map[SELECT * FROM `msb_content` WHERE `msb_content`.`id` = ? AND `msb_content`.`deleted_at` IS NULL ORDER BY `msb_content`.`id` LIMIT 1:0xc0001cb920 SELECT * FROM `msb_content` WHERE `msb_content`.`id` IN (?,?) AND `msb_content`.`deleted_at` IS NULL:0xc0001cbe30] + +} diff --git a/session_test.go b/session_test.go new file mode 100644 index 0000000..281c7e6 --- /dev/null +++ b/session_test.go @@ -0,0 +1,19 @@ +package gormExample + +import "testing" + +func TestSessionIssue(t *testing.T) { + SessionIssue() +} + +func TestSessionDB(t *testing.T) { + SessionDB() +} + +func TestSessionNew(t *testing.T) { + SessionNew() +} + +func TestSessionOptions(t *testing.T) { + SessionOptions() +} diff --git a/update.go b/update.go new file mode 100644 index 0000000..f84e5df --- /dev/null +++ b/update.go @@ -0,0 +1,94 @@ +package gormExample + +import ( + "fmt" + "gorm.io/gorm" + "log" +) + +func UpdatePK() { + var c Content + // 无主键 + if err := DB.Save(&c).Error; err != nil { + log.Fatalln(err) + } + // [12.300ms] [rows:1] INSERT INTO `msb_content` (`created_at`,`updated_at`,`deleted_at`,`subject`,`likes`,`views`,`publish_time`,`author_id`) VALUES ('2023-04-21 16:42:35.089','2023-04-21 16:42:35.089',NULL,'',0,0,'2023-04-21 16:42:35.089',0) ON DUPLICATE KEY UPDATE `updated_at`='2023-04-21 16:42:35.089',`deleted_at`=VALUES(`deleted_at`),`subject`=VALUES(`subject`),`likes`=VALUES(`likes`),`views`=VALUES(`views`),`publish_time`=VALUES(`publish_time`),`author_id`=VALUES(`author_id`) + fmt.Printf("%+v\n", c) + + // 具有主键ID + if err := DB.Save(&c).Error; err != nil { + log.Fatalln(err) + } + // [9.316ms] [rows:1] UPDATE `msb_content` SET `created_at`='2023-04-21 16:45:22.294',`updated_at`='2023-04-21 16:45:22.306',`deleted_at`=NULL,`subject`='',`likes`=0,`views`=0,`publish_time`='2023-04-21 16:45:22.293',`author_id`=0 WHERE `msb_content`.`deleted_at` IS NULL AND `id` = 61 + fmt.Printf("%+v\n", c) +} + +func UpdateWhere() { + // 更新的字段值数据 + // map推荐 + values := map[string]any{ + "subject": "Where Update Row", + "likes": 10001, + } + + // 执行带有条件的更新 + result := DB.Model(&Content{}). + //Omit("updated_at"). + Where("likes > ?", 100). + Updates(values) + if result.Error != nil { + log.Fatalln(result.Error) + } + + // 获取更新结果,更新的记录数量(受影响的记录数) + // 指的是修改的记录数,而不是满足条件的记录数 + log.Println("updated rows num: ", result.RowsAffected) +} + +func UpdateNoWhere() { + // 更新的字段值数据 + // map推荐 + values := map[string]any{ + "subject": "Where Update Row", + "likes": 1001, + } + + // 执行带有条件的更新 + result := DB.Model(&Content{}). + // 全局更新 + Where("1=1"). + Updates(values) + if result.Error != nil { + log.Fatalln(result.Error) + } + + // 获取更新结果,更新的记录数量(受影响的记录数) + // 指的是修改的记录数,而不是满足条件的记录数 + log.Println("updated rows num: ", result.RowsAffected) +} + +func UpdateExpr() { + // 更新的字段值数据 + // map推荐 + values := map[string]any{ + "subject": "Where Update Row", + // 值为表达式计算的结果时,使用Expr类型 + "likes": gorm.Expr("likes + ?", 10), + //"likes": "likes + 10", + // Incorrect integer value: 'likes + 10' for column 'likes' at row 1 + } + + // 执行带有条件的更新 + result := DB.Model(&Content{}). + Where("likes > ?", 100). + Updates(values) + // [17.011ms] [rows:51] UPDATE `msb_content` SET `likes`=likes + 10,`subject`='Where Update Row',`updated_at`='2023-04-21 17:28:45.498' WHERE likes > 100 AND `msb_content`.`deleted_at` IS NULL + + if result.Error != nil { + log.Fatalln(result.Error) + } + + // 获取更新结果,更新的记录数量(受影响的记录数) + // 指的是修改的记录数,而不是满足条件的记录数 + log.Println("updated rows num: ", result.RowsAffected) +} diff --git a/update_test.go b/update_test.go new file mode 100644 index 0000000..826334e --- /dev/null +++ b/update_test.go @@ -0,0 +1,19 @@ +package gormExample + +import "testing" + +func TestUpdatePK(t *testing.T) { + UpdatePK() +} + +func TestUpdateWhere(t *testing.T) { + UpdateWhere() +} + +func TestUpdateNoWhere(t *testing.T) { + UpdateNoWhere() +} + +func TestUpdateExpr(t *testing.T) { + UpdateExpr() +}