这是我参与「第五届青训营 」伴学笔记创作活动的第 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,'',falseor 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等方法用于路由注册
Hertz提供了路由组的能力,用于支持路由分组的功能
Hertz提供了Bind、Validate、BuildAndValidate函数用于进行参数绑定和校验
最后是一个笔记案例
kitex-examples/bizdemo/easy_note at main · cloudwego/kitex-examples (github.com)
总结
今天懂得了IDL的概念,以及RPC远程调用的方式。因为Gorm和Gin用过,所以今天看Gorm框架和Hertz框架的时候,用起来没有太大难度。