Go 框架三件套详解 | 青训营笔记

78 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

Go 框架三件套详解

GORM的基本使用

连接数据库

目前GORM官方支持的数据库为:MySQL, PostgreSQL, SQlite, SQL Server

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

func main() {
  dsn := "user:pass@tcp(127.0.0.1:3306)/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) // 通过数据的指针来创建 
user.ID             // 返回插入数据的主键 
result.Error        // 返回 
error result.RowsAffected // 返回插入记录的条数

如果要插入多条记录

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

通过标签 default 为字段定义默认值

type User struct {   
    ID   int64   
    Name string `gorm:"default:galeone"`  
    Age  int64  `gorm:"default:18"` 
} 

插入记录到数据库时,默认值 会被用于 填充值为 零值 的字段 但是如何防止插入时主键冲突呢

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

更多用法需要参考官方文档。

查询数据

// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

条件查询

// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';

// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';

// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

注意用First时可能查询不到数据会返回ErrRecordNotFound,所以更加推荐用Find进行查询

然后官方有句话

NOTE When querying with struct, GORM will only query with non-zero fields, that means if your field’s value is 0''false or other zero values, it won’t be used to build query conditions, for example:

db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users) 
// SELECT * FROM users WHERE name = "jinzhu"; 

To include zero values in the query conditions, you can use a map, which will include all key-values as query conditions, for example:

db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users) 
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0; 

意思就是结构体查询的时候,如果有字段为0或者为空,那么不参与条件查询,如果要包含零值,需要用map

更新数据

// 单列更新 
db.Model(&User{}).Where("active = ?", true).Update("name", "hello") 
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;

// 多列更新
// 根据 `struct` 更新属性,只会更新非零值的字段 
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false}) 
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;  

// 根据 `map` 更新属性 
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false}) 
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111; 

//更新选定字段
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;

//使用SQL表达式更新
// product 的 ID 是 `3`
db.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
// UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;

跟查询一样,用结构体更新只会更新非零值,如果要更新零值要用Map更新或者Select选择字段

删除数据

// Email 的 ID 是 `10` 
db.Delete(&email) 
// DELETE from emails where id = 10; 
db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{}) 
// DELETE from emails where email LIKE "%jinzhu%";  
db.Delete(&Email{}, "email LIKE ?", "%jinzhu%") 
// DELETE from emails where email LIKE "%jinzhu%";

gorm提供了软删除,利用gorm.deletedat 字段 拥有软删除能力的模型调用 Delete 时,记录不会从数据库中被真正删除。但 GORM 会将 DeletedAt 置为当前时间, 不能再通过普通的查询方法找到该记录。

事务

Gorm提供了Transaction方法用于自动提交事务

db.Transaction(func(tx *gorm.DB) error {   
    // 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')   
    if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {     
        // 返回任何错误都会回滚事务     
        return err  
    }    
    if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {    
    return err   
    }    
    // 返回 nil 提交事务   
    return nil 
})

当然也支持手动事务模式,begin,commit,rollback

Hook

Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。

钩子方法的函数签名应该是 func(*gorm.DB) error

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {   
    u.UUID = uuid.New()    
    if !u.IsValid() {    
        err = errors.New("can't save invalid data")   
    }   
    return 
}  
func (u *User) AfterCreate(tx *gorm.DB) (err error) {   
    if u.ID == 1 {     
        tx.Model(u).Update("role", "admin")   
    }   
    return 
}

如果任何Hook返回错误,Gorm将停止后续操作并回滚事务

性能提高

对于写操作(创建、更新、删除),为了确保数据的完整性,GORM 会将它们封装在一个事务里。但这会降低性能,可以在初始化时禁用这种方式

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{   
    SkipDefaultTransaction: true, 
})

执行任何 SQL 时都创建并缓存预编译语句,可以提高后续的调用速度

// 全局模式
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{  
    PrepareStmt: true, 
})

Kitex

由于本机是windows系统,所以不能进行相关测试 然后课程介绍了RPC远程调用框架Kitex的相关使用方法

IDL

接口描述语言。相当于用中立的接口来描述接口,使得不同平台运行的对象和不同语言编写的程序可以相互通信交流,比如Java写的接口和Python写的接口。

Thrift: thrift.apache.org/docs/idl

利用Kitex可以根据IDL生成相关的代码。

创建Client

import "example/kitex_gen/api/echo"
import "github.com/cloudwego/kitex/client"
...
c, err := echo.NewClient("example", client.WithHostPorts("0.0.0.0:8888"))
if err != nil {
  log.Fatal(err)
}

第一个参数为调用的 服务名,第二个参数为 options,用于传入参数

发起请求

import "example/kitex_gen/api"
...
req := &api.Request{Message: "my request"}
resp, err := c.Echo(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))
if err != nil {
  log.Fatal(err)
}
log.Println(resp)

首先创建了一个请求 req , 然后通过 c.Echo 发起了调用。

其第一个参数为 context.Context,通过通常用其传递信息或者控制本次调用的一些行为。

其第二个参数为本次调用的请求。

其第三个参数为本次调用的 options

关于更多的Kitex,参考快速开始 | CloudWeGo

Hertz

Hertz是一个类似于gin的http框架,用法很像。

package main

import (
	"context"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main() {
	h := server.Default()
	h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "Hello hertz!")
	})
	h.Spin()
}

curl --location --request GET '127.0.0.1:8888/hello这时候会得到"Hello hertz!"结果

Hertz提供了GET、POST、PUT、DELETE等方法用于路由注册

image.png

Hertz提供了路由组的能力,用于支持路由分组的功能

image.png

Hertz提供了Bind、Validate、BuildAndValidate函数用于进行参数绑定和校验

image.png

最后是一个笔记案例

kitex-examples/bizdemo/easy_note at main · cloudwego/kitex-examples (github.com)

总结

今天懂得了IDL的概念,以及RPC远程调用的方式。因为Gorm和Gin用过,所以今天看Gorm框架和Hertz框架的时候,用起来没有太大难度。