Gorm框架
安装
安装依赖:
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 使用名为
CreatedAt
和UpdatedAt
字段来自动跟踪记录的创建和更新时间。
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 允许为BeforeSave
、 BeforeCreate
、 AfterSave
、 AfterCreate
实现用户定义的钩子。这些钩子方法会在创建记录时被调用: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 提供了First
、 Take
、 Last
方法从数据库中检索单个对象,它在查询数据库时添加了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生态
- GORM代码生成工具:https://github.com/go-gorm/gen
- GORM分片库方案:https://github.com/go-gorm/sharding
- GORM手动索引:https://github.com/go-gorm/hints
- GORM乐观锁:https://github.com/go-gorm/optimisticlock
- GORM读写分离:https://github.com/go-gorm/dbresolver
- 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 提供了 GET
、POST
、PUT
、DELETE
、ANY
等方法用于注册路由。
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)