GORM、Gen 实践 | 青训营笔记

1,557 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记

任务目标

任务一: 首先实现一个脚本工具: 包含一个 User struct, 只包含 UUID string, Name string,Age int,Version int 四个字段,在脚本中使用 gorm + mysql 初始化 DB, 并使用初始化后的 DB 的 AutoMigrate 迁移数据表。

任务二: 然后完成一个 Gen (github.com/go-gorm/gen/) 项目 实现:

  • 基于上面创建的数据库及表名,通过 Gen 的自动同步库表功能生成 struct People,并给该 struct 生成基本 CRUD 方法。

  • 基于 OnConflict Upsert 功能实现 100 个随机用户的创建,其中需要包含重复的 UUID 用户的 Upsert, 在 Upsert 时,如果遇到重复 UUID 中,需要将 Version 更新为 Version + 1。

  • 最后再通过一条自定义的Raw SQL 实现,将数据按 Version 分组,并取出 Version 最高的一组的用户总数的功能,该 Raw SQL 需要通过自定义查询方法的形式实现,需要给 People 生成相应的方法名: GetMaxVersionCount

任务的具体实现代码已经在我的github上了

任务1

任务一较为简单,只需要掌握gorm的基本用法即可。

gorm.Open方法会返回一个*DB的结构体和error,同时自动初始化数据库

DB.AutoMigrate方法会自动的生成和传入结构体格式相同的表。参数可以是多个,表格命名为结构体的名字

image.png

image.png

package main

import (
	"log"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type User struct {
	UUID    string
	Name    string
	Age     int
	Version int
}

//作业1
func Init() {
	drive, err := gorm.Open(
		mysql.New(mysql.Config{
			DSN:                       "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local",
			DefaultStringSize:         256,   // string 类型字段的默认长度
			DisableDatetimePrecision:  true,  // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
			DontSupportRenameIndex:    true,  // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
			DontSupportRenameColumn:   true,  // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
			SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
		}), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	err = drive.AutoMigrate(&User{})
	if err != nil {
		log.Fatal(err)
		return
	}

}

func main() {
	Init()
}

任务二

自动生成struct People,并生成基本CRUD方法

*DB提供的GenerateModelAs可以自动生成相应表结构的struct,返回的是BaseStruct的指针,传入的第一个参数为表名。其余参数可缺省,则生成的结构体名和表名一致。若有多个参数,则第二个参数为生成的结构体的名字,后续的为属性名

ApplyBasic接收

image.png

package main

import (
	"gorm.io/driver/mysql"
	"gorm.io/gen"
	"gorm.io/gorm"
)

func generateModel() {
	g := gen.NewGenerator(gen.Config{
		OutPath: "../../dal/query",
	})

	db, _ := gorm.Open(mysql.Open("root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"))
	g.UseDB(db)

	//读取数据库中的users表,生成People结构体,并返回BaseStruct地址
	// g.GenerateModelAs("users", "People")
        
	//ApplyBasic的参数是&BaseStruct,生成并生成对应的基本CRUD方法
	g.ApplyBasic(g.GenerateModelAs("users", "Person"))
	g.Execute()
}
func main() {
	generateModel()
}

基于 OnConflict Upsert 功能实现 100 个随机用户的创建

对于这部操作,在我使用OnConflict方法更新Version会有各种报错,所以我选择了直接在插入之前查询是否有相同的UUID,若没有则直接插入,若有则version+1

因为要包含重复的UUID,所以我选择生成50个不同的UUID,再随机从50个中选择一个作为创建的用户的UUID

package biz

import (
	"context"
	"go-gorm/gen/dal"
	"go-gorm/gen/dal/model"
	"go-gorm/gen/dal/query"
	"log"
	"math/rand"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

//插入100条数据,重复的UUID则Version+1
func InsertPerson() {
	conn := dal.Connect()
	query := query.Use(conn)
	// fmt.Printf("query.Person.ALL: %v\n", query.Person.ALL)

	UUID := make([]string, 50)
	for i := range UUID {
		u, err := uuid.NewUUID()
		if err != nil {
			log.Fatal(err)
		}
		UUID[i] = u.String()
	}

	for i := 0; i < 100; i++ {
		j := rand.Intn(50)

		u, err := query.Person.WithContext(context.Background()).Debug().Where(query.Person.UUID.Eq(UUID[j])).First()
		if err != nil {
			if err == gorm.ErrRecordNotFound {
				err = query.Person.WithContext(context.Background()).Create(&model.Person{
					UUID:    UUID[j],
					Name:    "2",
					Age:     1,
					Version: 1,
				})
				if err != nil {
					log.Printf("Create fail:%v\n", err)
				}
				continue
			}
			log.Printf("Find fail:%v\n", err)
			continue
		}

		_, err = query.Person.WithContext(context.Background()).Where(query.Person.UUID.Eq(UUID[j])).Update(query.Person.Version, u.Version+1)
		if err != nil {
			log.Printf("Update fail:%v\n", err)
			continue
		}
	}
}

取出Version最高的一组的用户的数量

这一步就很简单了,分组再排序就可

func (p personDo) GetMaxVersionCount() (result *model.Person, err error) {
	var generateSQL strings.Builder
	generateSQL.WriteString("select * from users order by version desc ")

	var executeSQL *gorm.DB
	executeSQL = p.UnderlyingDB().Raw(generateSQL.String()).Take(&result)
	err = executeSQL.Error
	return
}