这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
三件套的使用
建议查文档,这里只是短暂记录下
Gorm
基本使用
Gorm Model的约定:
- Gorm使用名为ID的字段为主键
- 使用结构体的蛇形负数的表名
- 字段名的蛇形为列名
- 使用CreateAt、UpdateAt字段作为创建、更新时间
定义Gorm Model
type Product struct {
Code string
Price uint
}
func (p Product) TableName() string {
return "product"
}
连接数据库:
db, err := gorm.Open(
mysql.Open("user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8&mb4&parseTime=True&loc=Local"),
&gorm.Config{})
if err != nil {
panic("faild to connect database")
}
增删改查:
db.Create(&Product{Code: "D42", Price: 100})
var product Product
db.First(&product, 1)
db.First(&product, "code = ?", "D42")
db.Model(&product).Update("Price", 200)
db.Model(&product).Updates(Product{Price: 200, Code: "F42"})
db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
db.Delete(&product, 1)
支持的数据库
Gorm目前支持MySQL、SQLServer、PostgreSQL、SQLite
创建数据
type Product struct {
ID uint `gorm: "primarykey"`
Code string `gorm: "column": "code`
Price uint `gorm:"column": "user_id"`
}
func main() {
// 连接数据库
db, err := gorm.Open(mysql.Open("username:password@tcp(localhost:9910)/database?charset=utf8"),
&gorm.Config{})
if err != nil {
panic("faild to connect database")
}
p := &Product{Code: "D42"}
// 添加单条数据
res := db.Create(p)
fmt.Println(res.Error)
fmt.Println(p.ID)
// 添加多条数据
products := []*Product{{Code: "D41"}, {Code: "D42"}, {Code: "D43"}}
res = db.Create(products)
fmt.Println(res.Error)
for _, p := range products {
fmt.Println(p.ID)
}
}
如果产生数据冲突:使用clause.OnConflict处理数据冲突
p := &product{Code: "D42", ID: 1}
db.Clauses(clause.OnConflict{DoNoting: true}).Create(&p)
使用默认值:通过使用default标签为字段定义默认值
type User struct {
ID int64
Name string `gorm: "default:galeone"`
Age int64 `gorm: default:18`
}
查询数据
使用First注意:
- 查询不到数据会返回ErrRecordNotFound
- 使用Find查询多条数据,查询不到数据不会返回错误
使用结构体作为查询条件:
GORM只会查询非0值字段:这意为着如果字段值为“0”、“false”或者其他0值,该字段不会用于构建查询条件,使用Map来构建查询条件
// 获取第一条记录
u := &User{}
db.First(u)
users := make([]*User, 0)
// SELECT * FROM users WHERE age > 10
result := db.Where("age > 10").Find(&users)
fmt.Println(result.RowsAffected) // 返回查找到的记录数
fmt.Println(result.Error)
// SELECT * FROM users WHERE name IN ('jinzhu', 'jinzhu2')
db.Where("name IN ?", []string{"jinzhu", "jinzhu2"}).Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%'
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name = jinzhu AND age >= 22
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = "jinzhu"
db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0
db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
更新数据
使用Struct更新时,只会更新非0值,如果需要更新零值可以使用Map更新或使用Select字段
// 条件更新单个列
// UPDATE users SET name = "hello", updated_at = 'xxxx-xx-xx xx:xx:xx' WHERE age > 10
db.Model(&User{ID: 111}).Where("age > ?", 18).Update("name", "hello")
// 更新多个列。根据struct更新属性,只会更新非零值的字段
// UPDATE users SET name = "hello", age = 18, updateed_at 'xxx' WHERE id = 111
db.Model(&User{ID: 111}).Updates(User{Name: "hello", Age: 18})
// 根据map更新属性
// UPDATE users SET name = 'hello', age = '18', actived = false, updated_at = 'xxx' WHERE id = 111
db.Model(&User{ID: 111}).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
// 更新选定字段
// UPDATE users SET name = 'hello' WHERE id = 111
db.Model(&User{ID: 111}).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
// SQL表达式更新
// UPDATE users SET "age" = age * 2 + 100, "updated_at" = xxx WHERE id = 111
db.Model(&User{ID: 111}).Update("age", gorm.Expr("age * ? + ?", 2, 100))
删除数据
物理删除
// DELETE FROM users WHERE id = 10
db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10
db.Delete(&User{}, "10")
// DELETE FROM users WHERE id IN (1, 2, 3)
db.Delete(&User{}, []int{1, 2, 3})
// DELETE from users where name LIKE "%jinzhu%"
db.Where("name LIKE ?", "%jinzhu%").Delete(User{})
// DELETE from users WHERE name LIKE "%jinzhu%"
db.Delete(User{}, "name LIKE ?", "%jinzhu%")
软删除
Gorm提供了gorm.DeleteAt用于帮助用户实现软删除
拥有软删除能力的Model调用Delete时,记录不会被数据真正删除,但Gorm会将DeleteAt置为当前时间,冰倩不能再通过正常的查询方法找到该记录
使用UnScoped可以查询到软删除的数据
db.UnScoped().Where("age = 20").Find(&users)
事务
Gorm提供了Begin、Commit、Rollback方法用于使用事务
tx := db.Begin() // 开启事务
// 执行db操作
if err = tx.Create(&User{Name: "name"}).Error; err != nil {
tx.Rollback()
return
}
if err = tx.Create(&User{Name: "name1"}).Error; err != nil {
tx.Rollback()
return
}
// 提交事务
tx.Commit()
另外还提供了Transaction方法用于自动提交事务
GORM HOOK
HOOK是在创建、查询、更新、删除等操作之前后,自动调用的函数。
如果任何Hook返回错误,Gorm将停止后续的操作并回滚事务
type User struct {
ID int64
Name string `gorm: "default:galeone"`
Age int64 `gorm: default:18`
}
func (u *User) BeforeCreate(tx *gorm.db) (err Error) {
if u.Age < 0 {
return errors.New("Xxxx")
}
return
}
func (u *User) AfterCreate(tx * gorm.db) (err Error) {
xxxx
}
Gorm性能提高
对于写操作,为了保证数据的完整性,GORM会将它们风状态事务内运行,但这会降低性能,可以使用SkipDefaultTransaction关闭默认事务
使用PrepareStmt缓存预编译语句可以提高后续调用的速度
db, err := gorm.Open(mysql.Open("username:password@tcp(localhost:9910)/database?charset=utf8"),
&gorm.Config{
SkipDefaultTransaction: true,
PrepareStmt: true},
)
更多
Kitex
使用IDL定义接口与服务
namespace go api
struct Request {
1: string message
}
struct Response {
1: string message
}
service Echo {
Response echo(1 :Request req)
}
使用kitex生成代码
kitex -module example -service example note.thrift
目录介绍:
- build.sh:构建脚本
- kitex_gen:IDL内容相关的生成代码,主要是最基础的Server/Client代码
- main.go:程序入口
- handler.go:用户在该文件里实现IDL
- server:定义的方法
基本使用
服务默认监听8888端口
package main
import (
"context"
api "example/kitex_gen/api"
)
// EchoImpl implements the last service interface defined in the IDL.
type EchoImpl struct{}
// Echo implements the EchoImpl interface.
func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
// TODO: Your code here...
return
}
Kitex Client发起请求
创建client目录,在里面创建main.go
package main
import (
"context"
"example/kitex_gen/api"
"example/kitex_gen/api/echo"
"log"
"time"
"github.com/cloudwego/kitex/client"
)
func main() {
// 创建客户端
c, err := echo.Client("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.WithHostPorts(3*time.Second))
if err != nil {
log.Fatal(err)
}
log.Fatal(resp)
}
Kitex服务的注册与发现
目前Kitex的服务注册与发现已经对接了的主流的服务注册与发现中心,如ETCD,Nacos等
type HelloImpl struct{}
func (h *HelloImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
resp = &api.Response{
Message: req.Message
}
return
}
// 服务端
func main() {
r, err := etcd.NewEtcdRegister([]string{"127.0.0.1:2379"})
if err != nil{
log.Fatal(err)
}
server := hello.NewServer(new(HelloImpl), server.WithRegistry(r), server.WithServerBasicInfo(&rpcinfo,EndpointBasicInfo {
ServiceName: "Hello",
}))
err = server.Run()
if err != nil {
log.Fatal(err)
}
}
// 客户端
func main() {
r, err := etcd.NewEtcdResolver([]string{"127.0.0.1:2379"})
if err != nil {
log.Fatal(err)
}
client := hello.MustNewClient("hello", client.WithResolver(r))
for {
ctx, cancel := context.WithTimeout(context.Background(), time.Second * 3)
resp, err := client.Echo(ctx, &api.Request{Message: "Hello"})
cancel()
if err != nil {
log.Fatal(err)
}
log.Println(resp)
time.Sleep(time.Second)
}
}
Kitex生态
Hertz
Hertz是一个HTTP框架
基本使用
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(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"message": "pong"})
})
h.Spin()
}
Hertx路由
路由:路由指导报文转发的路径信息,通过路由可以确认转发IP报文的路径。路由设备是依据路由转发报文到达目的网段的网络设备,最常见的路由设备:路由器。路由器维护着一张表,保存着路由信息。
Hertz提供了GET、POST、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 )的能力,用于支持路由分组的功能,同时中间件也可以注册到路由组上。
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"))
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()
}
Hertz代码生成工具
提供代码生成工具Hz,也可以使用接口定义语言IDL生成对应的基础服务代码
文件的结构: