高级查询
智能选择字段
GORM 允许通过 Select
方法选择特定的字段,如果您在应用程序中经常使用此功能,你也可以定义一个较小的结构体,以实现调用 API 时自动选择特定的字段
type User struct { ID uint Name string Age int Gender string // 假设后面还有几百个字段... } type APIUser struct { ID uint Name string } // 查询时会自动选择 `id`, `name` 字段 db.Model(&User{}).Limit(10).Find(&APIUser{}) // SELECT `id`, `name` FROM `users` LIMIT 10
子查询
子查询可以嵌套在查询中,GORM 允许在使用 *gorm.DB
对象作为参数时生成子查询
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders"); subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users") db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results) // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
From子查询
GORM 允许您在 Table
方法中通过 FROM 子句使用子查询
db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{}) // SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 subQuery1 := db.Model(&User{}).Select("name") subQuery2 := db.Model(&Pet{}).Select("name") db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) // SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
Group条件
使用 Group 条件可以更轻松的编写复杂 SQL
db.Where( db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")), ).Or( db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"), ).Find(&Pizza{}).Statement // SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")
更新
保存所有字段
Save
会保存所有的字段,即使字段是零值
db.First(&user) user.Name = "jinzhu 2" user.Age = 100 db.Save(&user) // UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
更新单个列
当使用 Update
更新单列时,需要有一些条件,否则将会引起错误 ErrMissingWhereClause
。当使用 Model
方法,并且值中有主键值时,主键将会被用于构建条件
// 条件更新 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; // User 的 ID 是 `111` db.Model(&user).Update("name", "hello") // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111; // 根据条件和 model 的值进行更新 db.Model(&user).Where("active = ?", true).Update("name", "hello") // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
更新多列
Updates
方法支持 struct
和 map[string]interface{}
参数。当使用 struct
更新时,默认情况下,GORM 只会更新非零值的字段
// 根据 `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
更新选定字段
如果想要在更新时选定、忽略某些字段,您可以使用 Select
、Omit
// Select with Map // 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; db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false}) // UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111; // Select with Struct (select zero value fields) db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0}) // UPDATE users SET name='new_name', age=0 WHERE id=111; // Select all fields (select all fields include zero value fields) db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0}) // Select all fields but omit Role (select all fields include zero value fields) db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
批量更新
如果尚未通过 Model
指定记录的主键,则 GORM 会执行批量更新
// 根据 struct 更新 db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18}) // UPDATE users SET name='hello', age=18 WHERE role = 'admin'; // 根据 map 更新 db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18}) // UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);
获取更新的记录数
// 通过 `RowsAffected` 得到更新的记录数 result := db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18}) // UPDATE users SET name='hello', age=18 WHERE role = 'admin'; result.RowsAffected // 更新的记录数 result.Error // 更新的错误
删除
删除一条记录
删除一条记录时,删除对象需要指定主键,否则会触发 批量 Delete
// Email 的 ID 是 `10` db.Delete(&email) // DELETE from emails where id = 10; // 带额外条件的删除 db.Where("name = ?", "jinzhu").Delete(&email) // DELETE from emails where id = 10 AND name = "jinzhu";
根据主键删除
GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字(如以下例子。也可以使用字符串——译者注)
db.Delete(&User{}, 10) // DELETE FROM users WHERE id = 10; db.Delete(&User{}, "10") // DELETE FROM users WHERE id = 10; db.Delete(&users, []int{1,2,3}) // DELETE FROM users WHERE id IN (1,2,3);
批量删除
如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录
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%";
返回删除行的数据
// 返回所有列 var users []User DB.Clauses(clause.Returning{}).Where("role = ?", "admin").Delete(&users) // DELETE FROM `users` WHERE role = "admin" RETURNING * // users => []User{{ID: 1, Name: "jinzhu", Role: "admin", Salary: 100}, {ID: 2, Name: "jinzhu.2", Role: "admin", Salary: 1000}} // 返回指定的列 DB.Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Delete(&users) // DELETE FROM `users` WHERE role = "admin" RETURNING `name`, `salary` // users => []User{{ID: 0, Name: "jinzhu", Role: "", Salary: 100}, {ID: 0"", Salary: 1000}}
软删除
如果模型包含了一个 gorm.deletedat
字段(gorm.Model
已经包含了该字段),它将自动获得软删除的能力!
拥有软删除能力的模型调用 Delete
时,记录不会从数据库中被真正删除。但 GORM 会将 DeletedAt
置为当前时间, 并且你不能再通过普通的查询方法找到该记录。
// user 的 ID 是 `111` db.Delete(&user) // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111; // 批量删除 db.Where("age = ?", 20).Delete(&User{}) // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20; // 在查询时会忽略被软删除的记录 db.Where("age = 20").Find(&user) // SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
查询被软删除的记录
可以使用
Unscoped
找到被软删除的记录
db.Unscoped().Where("age = 20").Find(&users) // SELECT * FROM users WHERE age = 20;
事务
禁用默认事务
为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,可以在初始化时禁用它,这将获得大约 30%+ 性能提升。
// 全局禁用 db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ SkipDefaultTransaction: true, }) // 持续会话模式 tx := db.Session(&Session{SkipDefaultTransaction: true}) tx.First(&user, 1) tx.Find(&users) tx.Model(&user).Update("Age", 18)
事务开启
要在事务中执行一系列操作,一般流程如下
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 })
嵌套事务
GORM 支持嵌套事务,您可以回滚较大事务内执行的一部分操作
db.Transaction(func(tx *gorm.DB) error { tx.Create(&user1) tx.Transaction(func(tx2 *gorm.DB) error { tx2.Create(&user2) return errors.New("rollback user2") // Rollback user2 }) tx.Transaction(func(tx2 *gorm.DB) error { tx2.Create(&user3) return nil }) return nil })
手动事务
Gorm 支持直接调用事务控制方法(commit、rollback)
// 开始事务 tx := db.Begin() // 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db') tx.Create(...) // ... // 遇到错误时回滚事务 tx.Rollback() // 否则,提交事务 tx.Commit()
Gorm性能提高
使用PrepareStmt
缓存预编译语句可以提高后续调用的速度,提高大约35%左右。
db , err := gorm.Open(mysql.Open("username:password@tcp(localhost:9910)/database?charset=utf8"),&gorm.Config{ PrepareStmt: true}
Kitex
安装
Kitex目前对Windows的支持不完善,建议使用虚拟机或WSL2
安装代码生成工具
go install github.com/cloudwego/tool/cmd/kitex@latest go install github.com/cloudwego/thriftgo@latest
使用
kitex
是 Kitex 框架提供的用于生成代码的一个命令行工具。目前,kitex 支持 thrift 和 protobuf 的 IDL,并支持生成一个服务端项目的骨架。
编写IDL
IDL是什么:IDL 全称是 Interface Definition Language,接口定义语言
为什么使用IDL:要进行 RPC,就需要知道对方的接口是什么,需要传什么参数,同时也需要知道返回值是什么样的,就好比两个人之间交流,需要保证在说的是同一个语言、同一件事。 这时候,就需要通过 IDL 来约定双方的协议,就像在写代码的时候需要调用某个函数,我们需要知道函数签名一样。
首先我们需要编写一个 IDL,这里以 thrift IDL 为例。
首先创建一个名为 echo.thrift
的 thrift IDL 文件。
然后在里面定义我们的服务
namespace go api struct Request { 1: string message } struct Response { 1: string message } service Echo { Response echo(1: Request req) }
生成echo服务代码
有了 IDL 以后我们便可以通过 kitex 工具生成项目代码了,执行如下命令:
ruby
复制代码
$ kitex -module example -service example echo.thrift
上述命令中,-module
表示生成的该项目的 go module 名,-service
表明我们要生成一个服务端项目,后面紧跟的 example
为该服务的名字。最后一个参数则为该服务的 IDL 文件。
生成后的项目结构如下:
. |-- build.sh |-- echo.thrift |-- handler.go |-- kitex_gen | `-- api | |-- echo | | |-- client.go | | |-- echo.go | | |-- invoker.go | | `-- server.go | |-- echo.go | `-- k-echo.go |-- main.go `-- script |-- bootstrap.sh `-- settings.py
编写echo服务逻辑
需要编写的服务端逻辑都在 handler.go
这个文件中
package main import ( "context" "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 }
这里的 Echo
函数就对应了我们之前在 IDL 中定义的 echo
方法。
现在让我们修改一下服务端逻辑,让 Echo
服务起到作用。
func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) { return &api.Response{Message: req.Message}, nil