跳到主要内容

Gorm框架

安装

官网:https://gorm.io/docs/

安装依赖:

go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

使用:

package main

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

type Product struct {
gorm.Model
Code string
Price uint
}

func main() {
db, err := gorm.Open(mysql.Open("root:123456@tcp(127.0.0.1:3306)/test2"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}

// Migrate the schema
db.AutoMigrate(&Product{})

// Create
db.Create(&Product{Code: "D42", Price: 100})

// Read
var product Product
db.First(&product, 1) // find product with integer primary key
db.First(&product, "code = ?", "D42") // find product with code D42

// Update - update product's price to 200
db.Model(&product).Update("Price", 200)
// Update - update multiple fields
db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // non-zero fields
db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})

// Delete - delete product
db.Delete(&product, 1)
}

连接到数据库

使用DSN连接(DSN 是数据源名称的缩写,它是一种用于标识和定位数据源的命名方式):

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{})

例如:

db, err := gorm.Open(mysql.Open("root:123456@tcp(127.0.0.1:3306)/test2"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}

模型定义

type Product struct {
gorm.Model
Code string `gorm:"column:code"`
Price uint
}

其中gorm.Model的内容为:

type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}

TableName() 方法的作用是在使用 gorm 库与数据库进行交互时作为表名使用,(默认为结构体的蛇形复数作为表名)。gorm 库在执行数据库操作(比如查询、插入、更新等)时,需要知道对应的数据库表名。通过在 Product 结构体上定义这个 TableName() 方法并返回正确的表名,gorm 库就能够准确地找到要操作的数据库表,确保数据的正确读写。

一些使用惯例:

  • 主键:GORM 使用名为ID的字段作为每个模型的默认主键。

  • 表名称:默认情况下,GORM 将结构名称转换为snake_case并将其复数化为表名称。例如, User结构成为数据库中的users

  • 列名:GORM 自动将数据库中列名的结构体字段名转换为snake_case 。例如:

    Code  string `gorm:"column:code1"`
  • 时间戳字段:GORM 使用名为CreatedAtUpdatedAt字段来自动跟踪记录的创建和更新时间。

CRUD接口

Create

插入数据后会返回主键id

func testCreate(db *gorm.DB) {
db.AutoMigrate(&Product{})
product := Product{Code: "10", Price: 20}
db.Create(&product)
fmt.Println("product created id=>", product.ID)
//插入指定字段
db.Select("code").Create(&product)
}

批量插入数据:

	var products = []Product{
{Code: "11", Price: 21},
{Code: "12", Price: 22},
}
db.Create(&products)
for _, product := range products {
fmt.Println(product.ID)
}

Hook函数:

GORM 允许为BeforeSaveBeforeCreateAfterSaveAfterCreate实现用户定义的钩子。这些钩子方法会在创建记录时被调用:https://gorm.io/docs/hooks.html

func (p *Product) BeforeCreate(tx *gorm.DB) (err error) {
p.Price += 100
return
}

func testCreate(db *gorm.DB) {
db.AutoMigrate(&Product{})
db.Create(&Product{Code: "D42", Price: 100})
}

GORM 支持从map[string]interface{}[]map[string]interface{}{}创建,例如:

db.Model(&Product{}).Create(
map[string]interface{}{
"code": "D42",
})

Query

GORM 提供了FirstTakeLast方法从数据库中检索单个对象,它在查询数据库时添加了LIMIT 1条件,如果没有找到记录,则会返回错误ErrRecordNotFound

func testQuery(db *gorm.DB) {
db.Create(&Product{Code: "1111", Price: 1000})
//查询
var product Product
db.First(&product, "code = ?", "1111") // find product with code 1111
fmt.Println("product:", product)
}

查询结果到一个列表中:

func testQuery(db *gorm.DB) {
var products []Product
db.Where("price>?", 0).Find(&products)

for _, product := range products {
fmt.Println("ID: ", product.ID)
}
}

Update

update使用的是save来进行update,保存将在执行更新 SQL 时保存所有字段

func testQuery(db *gorm.DB) {
var product = Product{}
db.First(&product, 1) // find product with integer primary key
product.Price = 200
db.Save(&product)
}

当使用Update更新单个列时,它需要有任意条件,否则会引发错误ErrMissingWhereClause

func testQuery(db *gorm.DB) {
db.Model(&Product{}).Where("price=?", 200).Update("code", "L1212")
}

Delete

删除记录时,删除的值需要有主键,否则会触发

func testDelete(db *gorm.DB) {
db.Where("code = ?", "L1212").Delete(&Product{})
db.Delete(&Product{}, 2)
}

Raw SQL

使用Scan 执行查询语句SQL

var products []Product
db.Raw("select id,code from product").Scan(&products)
for i, product := range products {
fmt.Println(i, product.ID, product.Code)
}

使用Exec执行原生SQL

db.Exec("insert into product (code, price) values (?, ?)", "L1212", 1000)

事务

GORM 在事务内执行写入(创建/更新/删除)操作以确保数据一致性,如果不需要,可以在初始化时禁用它,之后您将获得约 30%+ 的性能提升

初始化禁用:

db, err := gorm.Open(mysql.Open("root:123456@tcp(127.0.0.1:3306)/test2?parseTime=true"), &gorm.Config{
SkipDefaultTransaction: true,
})

要在事务中执行一组操作,一般流程如下:

db.Transaction(func(tx *gorm.DB) error {
// Create
if err := tx.Create(&Product{Code: "D42", Price: 100}).Error; err != nil {
return err
}
var product Product
tx.First(&product, 1) // find product with integer primary key
tx.Model(&product).Update("Price", 200)
tx.Delete(&product, 1)
return nil
})

也可以手动开启事务,手动进行回滚:

tx := db.Begin()
if err := tx.Create(&Product{Code: "D42", Price: 100}); err.Error != nil {
tx.Rollback()
}
tx.Commit()

Gorm生态

  1. GORM代码生成工具:https://github.com/go-gorm/gen
  2. GORM分片库方案:https://github.com/go-gorm/sharding
  3. GORM手动索引:https://github.com/go-gorm/hints
  4. GORM乐观锁:https://github.com/go-gorm/optimisticlock
  5. GORM读写分离:https://github.com/go-gorm/dbresolver
  6. GORM OpenTelemetry扩展:https://github.com/go-gorm/opentelemetry
// idl/hello.thrift
namespace go hello.example

struct HelloReq {
1: string Name (api.query="name"); // 添加 api 注解为方便进行参数绑定
}

struct HelloResp {
1: string RespBody;
}


service HelloService {
HelloResp HelloMethod(1: HelloReq request) (api.get="/hello");
}

Kitex

官网:https://www.cloudwego.io/zh/docs/kitex/overview/

安装

安装代码生成工具:

go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
go install github.com/cloudwego/thriftgo@latest

IDL

IDL(Interface Definition Language)即接口定义语言。它主要用于描述软件组件之间的接口,这些接口定义了组件之间如何进行通信、传递数据和调用服务。

如果我们要使用 RPC 进行调用,就需要知道对方的接口是什么,需要传什么参数,同时也需要知道返回值是什么样的,就好比两个人之间交流,需要保证在说的是同一个语言、同一件事。IDL 就是为了解决这样的问题,通过 IDL 来约定双方的协议,就像在写代码的时候需要调用某个函数,我们需要知道 签名一样。

定义IDL:

namespace go api

struct Request {
1: string message
}

struct Response {
1: string message
}
service Echo {
Response echo(1: Request req)

}

生成代码:

kitex -module example -service example echo.thrift

创建Client,然后发送请求:

c, err := echo.NewClient("example", client.WithHostPorts("0.0.0.0:8888"))
if err != nil {
log.Fatal(err)
}

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

接入服务注册中心:

func main() {
r, err := etcd.NewEtcdRegistry([]string{"127.0.0.1:2379"})
if err != nil {
log.Fatal(err)
}

addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:8890")
svr := stock.NewServer(new(StockServiceImpl),
server.WithServiceAddr(addr),
// 指定 Registry 与服务基本信息
server.WithRegistry(r),
server.WithServerBasicInfo(
&rpcinfo.EndpointBasicInfo{
ServiceName: "example.shop.stock",
},
),
)

err = svr.Run()

if err != nil {
log.Println(err.Error())
}
}

Hertz

官网:https://www.cloudwego.io/zh/docs/hertz/

入门

package main

import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/common/utils"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main() {
h := server.Default()
h.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
c.JSON(consts.StatusOK, utils.H{"message": "pong"})
})

h.Spin()
}

安装依赖:

go mod tidy

运行后测试:curl http://127.0.0.1:8888/ping

路由

Hertz 提供了 GETPOSTPUTDELETEANY 等方法用于注册路由。

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(server.WithHostPorts("127.0.0.1:8080"))

h.StaticFS("/", &app.FS{Root: "./", GenerateIndexPages: true})

h.GET("/get", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "get")
})
h.POST("/post", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "post")
})
h.PUT("/put", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "put")
})
h.DELETE("/delete", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "delete")
})
h.PATCH("/patch", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "patch")
})
h.HEAD("/head", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "head")
})
h.OPTIONS("/options", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "options")
})
h.Any("/ping_any", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "any")
})
h.Handle("LOAD", "/load", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "load")
})
h.Spin()
}

路由组:Hertz 提供了路由组 ( Group ) 的能力,用于支持路由分组的功能,同时中间件也可以注册到路由组上。

	h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
v1 := h.Group("/v1")
v1.GET("/get", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "get")
})
v1.POST("/post", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "post")
})
v2 := h.Group("/v2")
v2.PUT("/put", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "put")
})
v2.DELETE("/delete", func(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusOK, "delete")
})
h.Spin()

路由优先级:静态路由>命名参数路由>通配参数路由

HTTP Client

Hertz提供了HTTP Client来发送HTTP请求

c, err := client.NewClient()
if err != nil {
return
}
status, body, err := c.Get(context.Background(), nil, "www.baidu.com")
fmt.Printf("status=%v, body=%v, err=%v\n", status, body, err)