Note08 Go 框架三件套详解(Web/RPC/ORM)| 青训营

68 阅读7分钟

序言

本文主要介绍了Go框架三件套:GORM、Kitex和Hertz的相关知识。

GORM是一个功能强大的ORM框架,已经存在并迭代了十多年。它能够支持数据库的操作和管理。

Kitex是字节跳动内部使用的Golang微服务RPC框架,具有高性能和强可扩展性的特点。它在字节内部被广泛使用,如果对微服务性能有要求且希望定制扩展融入自己的治理体系,Kitex是一个不错的选择。

Hertz是一个HTTP框架,参考了其他开源框架的优势,具有高易用性、高性能和高扩展性的特点。

针对上述三个框架,本文将分别介绍它们的基本使用方法。

在GORM的基本使用部分,首先定义了一个名为Student的结构体,用于存放数据库中操作的数据。然后,介绍了GORM的默认约定,例如使用ID字段作为主键、使用结构体名的蛇形命名作为表名等。接着,演示了如何连接MySQL数据库并新增数据。通过示例代码可以看出,首先需要连接数据库,然后使用db.AutoMigrate(&Student{})进行迁移,将结构体与数据库建立联系,最后使用db.Create(&student)新增数据。

三件套使用

Gorm的基本使用

对结构体Student的定义和通过实现Tabler接口来更改表名的示例:

type Student struct {
    Id        int64     `gorm:"primaryKey"`  // 指定id为主键,默认为 "ID"
    Name      string    `gorm:"column:name"` // 指定字段名为 "name"
    Password  string    `gorm:"column:password"`
    Age       int64     `gorm:"column:age"`
    CreatedAt time.Time `gorm:"column:create_at"` // 蛇形命名
}

// 实现Tabler接口,更改默认表名为 "student"
func (Student) TableName() string {
    return "student"
}

以上代码定义了一个名为Student的结构体,包含了Id、Name、Password、Age和CreatedAt等属性。在结构体中,通过使用标签(gorm tag)指定了字段的约束和特性,例如设置Id为主键、指定字段名为"name"等。另外,通过实现Tabler接口并重写TableName方法,我们可以将默认的表名"students"更改为"student"。

这样,根据GORM的约定,默认会使用结构体名的蛇形命名作为表名,在这个例子中,通过重写TableName方法,表名被修改为"student"。在数据库迁移或查询过程中,GORM会根据这些约定和定义来进行相应的操作。

Gorm的默认约定

GORM的默认约定包括以下内容:

  1. GORM将使用名为ID的字段作为主键。
  2. GORM将使用结构体的蛇形命名作为表名。例如,结构体Student对应的表名将默认为"students"。
  3. GORM将使用字段名的蛇形命名作为列名。例如,结构体Student中的字段Name将对应数据库表中的"name"列。
  4. GORM将使用CreatedAt和UpdatedAt字段来跟踪记录的创建和更新时间。

这些约定使得GORM能够在不需要显式配置的情况下,根据结构体的定义自动进行数据库操作。重新表达后,以上就是GORM的默认约定的内容。

连接MySql数据库 并新增数据

GORM支持MySQL、SQL SErver、PostgreSQL、SQLite数据库,此处以连接Mysql为例。

func main() {
    // 连接数据库
    dsn := "root:password@tcp(localhost:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
       fmt.Printf("数据库连接失败:%s", err.Error())
       return
    }

    // 迁移
    db.AutoMigrate(&Student{})

    // 新增数据
    student := Student{
        ID:        1,
        Name:      "xiaoming",
        Password:  "123456",
        Age:       13,
        CreatedAt: time.Now(),
    }
    result := db.Create(&student)
    if result.Error != nil {
        fmt.Printf("新增数据失败:%s", result.Error.Error())
        return
    }
    fmt.Println("执行的记录条数:", result.RowsAffected)
}

输出结果:

执行的有些条数: 1

以上示例中,我们首先通过gorm.Open打开MySQL数据库的连接。其中dsn是连接字符串,需要根据实际情况进行配置。然后,使用db.AutoMigrate(&Student{})进行迁移,将定义的Student结构体与数据库建立联系。接下来,创建一个Student实例并赋值相应的字段。最后,使用db.Create(&student)将数据插入到数据库中,并通过结果对象result获取执行的记录条数。

当代码运行后,将输出执行的记录条数。如果连接或新增数据过程中出现错误,将输出相应的错误信息。

数据库中创建了数据库表student,并新增了数据

image.png 使用GORM处理数据冲突以及批量新增数据:

// 处理数据冲突
student := Student{ID: 2, Name: "小舞"}
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&student)

// 批量新增数据
students := []Student{
    {ID: 2, Name: "张三", Password: "333333", Age: 33, CreatedAt: time.Now()},
    {ID: 3, Name: "李四", Password: "444444", Age: 44, CreatedAt: time.Now()},
    {ID: 4, Name: "王五", Password: "555555", Age: 55, CreatedAt: time.Now()},
}
result := db.Create(&students)
fmt.Println("执行的记录条数:", result.RowsAffected)

**

以上示例中,第一个部分演示了如何处理数据冲突。我们创建了一个名为student的Student实例,使用clause.OnConflict{DoNothing: true}来处理冲突,即在冲突发生时不执行任何操作,并通过db.Clauses().Create()将该实例插入到数据库。

第二个部分演示了批量新增数据的方法。我们定义了一个名为students的Student切片,其中包含了多个Student实例。通过db.Create()方法将这些实例一次性批量插入到数据库中,并通过result.RowsAffected获取执行的记录条数。

运行代码后,将输出执行的记录条数。在数据冲突处理的示例中,因为冲突发生并设置为不执行操作,所以输出的记录条数为0。在批量新增数据的示例中,因为插入了三条数据,所以输出的记录条数为3。

查看数据库

image.png

修改

// 修改数据
result := db.Model(&Student{}).Where("id = ?", 1).Update("name", "唐三")
if result.Error != nil {
    fmt.Printf("修改数据失败:%s", result.Error.Error())
    return
}
fmt.Println("执行的记录条数:", result.RowsAffected)

查询

在示例中,使用db.Model(&Student{})指定要操作的模型,并通过Where("id = ?", 1)指定要修改的记录条件,即ID为1。然后,使用Update("name", "唐三")将"name"字段更新为"唐三"。result.RowsAffected用于获取执行的记录条数。 image.png



提供了如下五种查询方式。


// 方式一:获取第一条记录(按主键升序)
var student1 Student
db.First(&student1)
fmt.Println("方式一 查询的student1为:", student1)

// 方式二:获取一条记录(未指定排序字段)
var student2 Student
db.Take(&student2)
fmt.Println("方式二 查询的student2为:", student2)

// 方式三:获取最后一条记录(按主键降序)
var student3 Student
db.Last(&student3)
fmt.Println("方式三 查询的student3为:", student3)

// 方式四:根据主键查询
var student4 Student
db.First(&student4, 3)
fmt.Println("方式四 查询的student4为:", student4)

// 方式五:查询全部记录
var students []Student
db.Find(&students)
fmt.Println("方式五 查询的所有学生数据如下:")
for _, student := range students {
    fmt.Println(student)
}

在示例中,我们通过不同的方式查询数据库中的记录。方式一使用First()方法获取第一条记录,方式二使用Take()方法获取一条记录(未指定排序字段),方式三使用Last()方法获取最后一条记录,方式四使用First()方法根据主键查询指定的记录,方式五使用Find()方法获取全部的记录。

在方式一至方式四的查询中,我们创建了相应的student变量,并通过查询方法给它们赋值。在方式五的查询中,我们创建了一个students切片,并通过Find()方法将所有的记录赋值给它。

完成查询后,我们通过fmt.Println()将查询结果输出到控制台。

删除

var students []Student
db.Find(&students)
fmt.Println("删除前数据如下:")
for i := 0; i < len(students); i++ {
    fmt.Println(students[i])
}

// 根据ID删除
student1 := Student{ID: 3}
db.Delete(&student1)

// 根据用户名删除
student2 := Student{}
db.Where("name = ?", "王五").Delete(&student2)

db.Find(&students)
fmt.Println("删除后数据如下:")
for i := 0; i < len(students); i++ {
    fmt.Println(students[i])
}

// 使用事务执行写入操作
db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&Student{ID: 5, Name: "小明"}).Error; err != nil {
        return err
    }

    if err := tx.Create(&Student{ID: 6, Name: "小红"}).Error; err != nil {
        return err
    }

    return nil // 返回nil提交事务
})

在示例中,我们首先通过Find()方法查询数据库中的学生数据,并输出到控制台。然后,我们使用Delete()方法根据ID和用户名进行删除操作。通过创建对应的student实例,并传递给Delete()方法来指定条件删除。完成删除后,我们再次使用Find()方法查询数据库并输出结果。

接下来,我们演示了使用事务执行写入操作的示例。通过db.Transaction()方法开启事务,在事务中执行创建学生的操作。如果在事务过程中发生错误,事务会自动回滚,否则会提交事务。

性能优化

  1. 关闭事务;
  2. 使用PrepareStmt 缓存预编译语句可以提高调用速度,可提升大约35%左右;

学习链接 gorm.cn/zh_CN/docs/…

GORM生态

GORM 拥有丰富的扩展生态,部分举例如下: image.png Gorm文档:gorm.cn