钩子
对象生命周期
Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。
如果已经为模型定义了指定的方法,它会在创建、更新、查询、删除时自动被调用
如果任何回调返回错误,GORM 将停止后续的操作并回滚事务
在 GORM 中,钩子方法(Hooks)允许你在创建、更新、删除、查询等操作之前或之后执行自定义逻辑
这些钩子方法在对象的生命周期中被自动调用,可以用于设置默认值、验证数据、记录日志等
钩子方法的函数签名应该是 func(*gorm.DB) error
Hook
创建对象
创建时可用的 hook
// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插入记录至 db
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务
代码示例:
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
// 设置uuid
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
}
注意 在 GORM 中保存、删除操作会默认运行在事务上, 因此在事务完成之前该事务中所作的更改是不可见的,如果您的钩子返回了任何错误,GORM 会将事务回滚,从而撤销所有在事务中进行的修改
- 开始事务:当执行保存或删除操作时,GORM 会自动开始一个事务。
- 执行操作:在事务中执行相应的数据库操作,如插入、更新或删除记录。
- 钩子方法:在操作前后,GORM 会调用相应的钩子方法(如
BeforeSave
、AfterSave
等)。- 错误检查:如果任何钩子方法返回了错误,GORM 会将事务回滚,撤销所有在事务中的操作。
- 提交事务:如果所有操作和钩子方法都成功执行,GORM 会提交事务,将更改永久保存到数据库。
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
if !u.IsValid() {
return errors.New("rollback invalid user")
}
return nil
}
更新对象
更新时可用的 hook
// 开始事务
BeforeSave
BeforeUpdate
// 关联前的 save
// 更新 db
// 关联后的 save
AfterUpdate
AfterSave
// 提交或回滚事务
代码示例:
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
if u.readonly() {
// 检查只读状态
err = errors.New("read only user")
}
return
}
// 在同一个事务中更新数据
func (u *User) AfterUpdate(tx *gorm.DB) (err error) {
if u.Confirmed {
tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("verfied", true)
}
return
}
删除对象
删除时可用的 hook
// 开始事务
BeforeDelete
// 删除 db 中的数据
AfterDelete
// 提交或回滚事务
代码示例:
// 在同一个事务中更新数据
func (u *User) AfterDelete(tx *gorm.DB) (err error) {
if u.Confirmed {
// 更新关联记录
tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("invalid", false)
}
return
}
查询对象
查询时可用的 hook
// 从 db 中加载数据
// Preloading (eager loading)
AfterFind
代码示例:
func (u *User) AfterFind(tx *gorm.DB) (err error) {
if u.MemberShip == "" {
u.MemberShip = "user" // 设置默认值
}
return
}
修改当前操作
func (u *User) BeforeCreate(tx *gorm.DB) error {
// 通过 tx.Statement 修改当前操作,例如:
tx.Statement.Select("Name", "Age")
// 添加冲突处理
tx.Statement.AddClause(clause.OnConflict{DoNothing: true})
// tx 是带有 `NewDB` 选项的新会话模式
// 基于 tx 的操作会在同一个事务中,但不会带上任何当前的条件
err := tx.First(&role, "name = ?", user.Role).Error
// SELECT * FROM roles WHERE name = "admin"
// ...
return err
}
事务
禁用默认事务
为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它,这将获得大约 30%+ 性能提升
GORM 提供了多种事务处理方式,包括嵌套事务、手动事务、保存点等GORM 提供了多种事务处理方式,包括嵌套事务、手动事务、保存点等
// 全局禁用
db, err := gorm.Open(mysql.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
})
嵌套事务
在嵌套事务中,内部事务的回滚不会影响外部事务的提交
因为嵌套事务实际上是通过保存点(SavePoint)来实现的
当内部事务回滚时,只会回滚到保存点,而不会影响外部事务的其他操作
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
})
// Commit user1, user3
手动事务
// 开始事务
tx := db.Begin()
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
tx.Create(...)
// ...
// 遇到错误时回滚事务
tx.Rollback()
// 否则,提交事务
tx.Commit()
特殊的示例
在函数中处理事务,并使用 defer
确保在发生错误时回滚
func CreateAnimals(db *gorm.DB) error {
// 事务一旦开始,就应该使用 tx 处理数据
tx := db.Begin()
// 捕获错误
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Error; err != nil {
return err
}
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
}
SavePoint、RollbackTo
GORM 提供了 SavePoint
、Rollbackto
来提供保存点以及回滚至保存点,例如:
tx := db.Begin()
tx.Create(&user1)
tx.SavePoint("sp1")
tx.Create(&user2)
tx.RollbackTo("sp1") // Rollback user2
tx.Commit() // Commit user1
数据库迁移
AutoMigrate
AutoMigrate 用于自动迁移您的 schema,保持您的 schema 是最新的。
注意: AutoMigrate 会创建表,缺少的外键,约束,列和索引,并且会更改现有列的类型(如果其大小、精度、是否为空可更改)。但 不会 删除未使用的列,以保护您的数据。
db.AutoMigrate(&User{})
db.AutoMigrate(&User{}, &Product{}, &Order{})
// 创建表时添加后缀
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
注意 AutoMigrate 会自动创建数据库外键约束,您可以在初始化时禁用此功能,例如:
// 禁用外键约束
db, err := gorm.Open(mysql.Open("gorm.db"), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
})
Migrator 接口
提供了更细粒度的数据库迁移操作,允许你手动控制表、列、约束和索引的创建、删除和修改
GORM 提供了 Migrator 接口,该接口为每个数据库提供了统一的 API 接口,可用来为您的数据库构建独立迁移,例如:
SQLite 不支持 ALTER COLUMN
、DROP COLUMN
,当你试图修改表结构,GORM 将创建一个新表、复制所有数据、删除旧表、重命名新表。
一些版本的 MySQL 不支持 rename 列,索引。GORM 将基于您使用 MySQL 的版本执行不同 SQL
type Migrator interface {
// AutoMigrate
AutoMigrate(dst ...interface{}) error
// Database
CurrentDatabase() string
FullDataTypeOf(*schema.Field) clause.Expr
// Tables
CreateTable(dst ...interface{}) error
DropTable(dst ...interface{}) error
HasTable(dst interface{}) bool
RenameTable(oldName, newName interface{}) error
// Columns
AddColumn(dst interface{}, field string) error
DropColumn(dst interface{}, field string) error
AlterColumn(dst interface{}, field string) error
HasColumn(dst interface{}, field string) bool
RenameColumn(dst interface{}, oldName, field string) error
MigrateColumn(dst interface{}, field *schema.Field, columnType *sql.ColumnType) error
ColumnTypes(dst interface{}) ([]*sql.ColumnType, error)
// Constraints
CreateConstraint(dst interface{}, name string) error
DropConstraint(dst interface{}, name string) error
HasConstraint(dst interface{}, name string) bool
// Indexes
CreateIndex(dst interface{}, name string) error
DropIndex(dst interface{}, name string) error
HasIndex(dst interface{}, name string) bool
RenameIndex(dst interface{}, oldName, newName string) error
}
当前数据库
返回当前使用的数据库名
dbName := db.Migrator().CurrentDatabase()
表
// 为 `User` 创建表
db.Migrator().CreateTable(&User{})
// 将 "ENGINE=InnoDB" 添加到创建 `User` 的 SQL 里去
db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&User{})
// 检查 `User` 对应的表是否存在
exists := db.Migrator().HasTable(&User{})
db.Migrator().HasTable("users")
// 如果存在表则删除(删除时会忽略、删除外键约束)
db.Migrator().DropTable(&User{})
db.Migrator().DropTable("users")
// 重命名表
db.Migrator().RenameTable(&User{}, &UserInfo{})
db.Migrator().RenameTable("users", "user_infos")
列
type User struct {
Name string
}
// 添加 name 字段
db.Migrator().AddColumn(&User{}, "Name")
// 删除 name 字段
db.Migrator().DropColumn(&User{}, "Name")
// 修改 name 字段
db.Migrator().AlterColumn(&User{}, "Name")
// 检查字段是否存在
exists := db.Migrator().HasColumn(&User{}, "Name")
type User struct {
Name string
NewName string
}
// 重命名字段
db.Migrator().RenameColumn(&User{}, "Name", "NewName")
db.Migrator().RenameColumn(&User{}, "name", "new_name")
// 获取字段(列)类型
columns, _ := db.Migrator().ColumnTypes(&User{})
db.Migrator().ColumnTypes(&User{}) ([]*sql.ColumnType, error)
约束
type UserIndex struct {
Name string `gorm:"check:name_checker,name <> 'jinzhu'"`
}
// 创建约束
db.Migrator().CreateConstraint(&User{}, "name_checker")
// 删除约束
db.Migrator().DropConstraint(&User{}, "name_checker")
// 检查约束是否存在
exists := db.Migrator().HasConstraint(&User{}, "name_checker")
索引
type User struct {
gorm.Model
Name string `gorm:"size:255;index:idx_name,unique"`
}
// 为 Name 字段创建索引
db.Migrator().CreateIndex(&User{}, "Name")
db.Migrator().CreateIndex(&User{}, "idx_name")
// 为 Name 字段删除索引
db.Migrator().DropIndex(&User{}, "Name")
db.Migrator().DropIndex(&User{}, "idx_name")
// 检查索引是否存在
exists := db.Migrator().HasIndex(&User{}, "Name")
db.Migrator().HasIndex(&User{}, "idx_name")
type User struct {
gorm.Model
Name string `gorm:"size:255;index:idx_name,unique"`
Name2 string `gorm:"size:255;index:idx_name_2,unique"`
}
// 修改索引名
db.Migrator().RenameIndex(&User{}, "Name", "Name2")
db.Migrator().RenameIndex(&User{}, "idx_name", "idx_name_2")
约束
GORM 会在自动迁移和创建表时创建约束,查看 约束 或 数据库索引 获取详情
-
唯一约束(Unique Constraint):确保字段的值是唯一的
-
检查约束(Check Constraint):限制字段的值必须满足某些条件
-
外键约束(Foreign Key Constraint):确保字段的值与另一个表中的主键值一致
在使用AutoMigrate或CreateTable 时,GORM会根据模型的定义自动创建约束
使用Migrator接口手动管理约束
约束的命名规则
GORM会自动生成约束名称,但可以通过gorm:“constraint” 标签自定义约束名称
type User struct { gorm.Model Name string `gorm:"unique;constraint:name_unique"` }
其他迁移工具
GORM 的 AutoMigrate 在大多数情况下都工作得很好,
更严格的迁移工具,GORM 提供一个通用数据库接口
通过这些工具和接口,可以灵活地管理数据库的迁移过程,确保数据库结构始终与应用程序逻辑保持一致
// returns `*sql.DB`
db.DB()
查看 通用接口 获取详情。
记录日志
Logger
Gorm 有一个 默认 logger 实现,默认情况下,它会打印慢 SQL 和错误
Logger 接受的选项不多,您可以在初始化时自定义它,例如:
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值 超过指定时间会被判定为慢SQL
LogLevel: logger.Silent, // Log level
Colorful: false, // 禁用彩色打印
},
)
// 全局模式
db, err := gorm.Open(mysql.Open("test.db"), &gorm.Config{
Logger: newLogger,
})
// 新建会话模式
tx := db.Session(&gorm.Session{Logger: newLogger})
tx.First(&user)
tx.Model(&user).Update("Age", 18)
日志级别
GORM 定义了这些日志级别:Silent
、Error
、Warn
、Info
logger.Silent
:不输出任何日志logger.Error
:只输出错误级别的日志logger.Warn
:输出警告和错误级别的日志logger.Info
:输出信息、警告和错误级别的日志logger.Debug
:输出调试、信息、警告和错误级别的日志
db, err := gorm.Open(mysql.Open("test.db"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
Debug
Debug 单个操作,将当前操作的 log 级别调整为 logger.Info
// Debug()方法可以用来临时将当前操作的日志级别调整为logger.Info
// 只对当前操作生效,不会影响其他操作的日志级别
db.Debug().Where("name = ?", "jinzhu").First(&User{})
在调试后恢复默认日志级别,可以使用Info()方法
db.Info().Where("name = ?", "jinzhu").First(&User{})
自定义 Logger
参考 GORM 的 默认 logger 来定义您自己的 logger
Logger 需要实现以下接口,它接受 context
,所以你可以用它来追踪日志
type Interface interface {
LogMode(LogLevel) Interface
Info(context.Context, string, ...interface{})
Warn(context.Context, string, ...interface{})
Error(context.Context, string, ...interface{})
Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error)
}
通用数据库接口sql.DB
GORM 提供了 DB
方法,可用于从当前 *gorm.DB
返回一个通用的数据库接口 *sql.DB
// 获取通用数据库对象 sql.DB,然后使用其提供的功能
sqlDB, err := db.DB()
// Ping
sqlDB.Ping()
// Close
sqlDB.Close()
// 返回数据库统计信息
sqlDB.Stats()
注意 如果底层连接的数据库不是
*sql.DB
,它会返回错误
连接池
// 获取通用数据库对象 sql.DB ,然后使用其提供的功能
sqlDB, err := db.DB()
// SetMaxIdleConns 用于设置连接池中空闲连接的最大数量。
sqlDB.SetMaxIdleConns(10)
// SetMaxOpenConns 设置打开数据库连接的最大数量。
sqlDB.SetMaxOpenConns(100)
// SetConnMaxLifetime 设置了连接可复用的最大时间。
sqlDB.SetConnMaxLifetime(time.Hour)
性能
选择字段
默认情况下,GORM 在查询时会选择所有的字段,您可以使用 Select
来指定您想要的字段
db.Select("Name", "Age").Find(&Users{})
或者定义一个较小的 API 结构体,使用 智能选择字段功能
type User struct {
ID uint
Name string
Age int
Gender string
// 假设后面还有几百个字段...
}
type APIUser struct {
ID uint
Name string
}
// 根据不同的API结构体动态选择字段,可以使用反射来获取结构体的字段名称
func getColumnsFromStruct(s interface{}) []string {
t := reflect.TypeOf(s)
var columns []string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
columns = append(columns, field.Name)
}
return columns
}
// 使用示例
columns := getColumnsFromStruct(APIUser{})
db.Model(&User{}).Select(columns).Limit(10).Find(&apiUsers)
// 查询时会自动选择 `id`、`name` 字段
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SELECT `id`, `name` FROM `users` LIMIT 10
// 存储在Find参数的变量中
// 查询时智能选择字段
var apiUsers []APIUser
// 只选择id和name字段
// clause.Columns是一个方便的结构体,用于指定要选择的字段
// 也可以直接使用字符串切片 Select([]string{"id", "name"})
db.Model(&User{}).Select(clause.Columns{Columns:[]string{"id","name"}}).Limit(10).Find(&apiUsers)
迭代、FindInBatches
用迭代或 in batches 查询并处理记录
FindInBatches
方法允许你分批查询和处理记录,从而避免一次性加载大量数据导致的内存问题
// 分批查询和处理记录
var users []User
// 指定每批查询的记录数
batchSize := 10
//回调函数用于处理每批数据
db.FindInBatches(&User{}, batchSize, func(tx *gorm.DB, batch int) error {
// 处理当前批次的数据
tx.Select(clause.Columns{Columns: []string{"id", "name"}}).Find(&users)
for _, user := range users {
println(user.ID, user.Name)
}
return nil
})
Index Hints
Index 用于提高数据检索和 SQL 查询性能。 Index Hints
向优化器提供了在查询处理过程中如何选择索引的信息。与 optimizer 相比,它可以更灵活地选择更有效的执行计划
import "gorm.io/hints"
// 使用索引提示查询
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// SELECT * FROM `users` USE INDEX (`idx_user_name`)
// 强制使用索引
db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{})
// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)"
// 忽略索引
db.Clauses(
hints.ForceIndex("idx_user_name", "idx_user_id").ForOrderBy(),
// 提示优化器忽略指定的索引进行分组操作
hints.IgnoreIndex("idx_user_name").ForGroupBy(),
).Find(&User{})
// SELECT * FROM `users` FORCE INDEX FOR ORDER BY (`idx_user_name`,`idx_user_id`) IGNORE INDEX FOR GROUP BY (`idx_user_name`)"
读写分离
通过读写分离提高数据吞吐量,查看 Database Resolver 获取详情
自定义数据类型
实现自定义数据类型
Scanner / Valuer
自定义的数据类型必须实现 Scanner 和 Valuer 接口,以便让 GORM 知道如何将该类型接收、保存到数据库
例如:
type JSON json.RawMessage
// 实现 sql.Scanner 接口,Scan 将 value 扫描至 Jsonb
// 用于从数据库中扫描值到json.RawMessage类型
func (j *JSON) Scan(value interface{}) error {
bytes, ok := value.([]byte)
if !ok {
return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
}
// 用于表示原始的 JSON 数据。它是一个 []byte 的别名,但提供了额外的方法来处理 JSON 数据
// 可以使用 json.RawMessage 来存储和检索 JSON 数据
result := json.RawMessage{}
err := json.Unmarshal(bytes, &result)
*j = JSON(result)
return err
}
// 实现 driver.Valuer 接口,Value 返回 json value
// 用于将json.RawMessage类型转换为数据库值
func (j JSON) Value() (driver.Value, error) {
if len(j) == 0 {
return nil, nil
}
return json.RawMessage(j).MarshalJSON()
}
type User struct {
ID uint
Name string
Attrs JSON `gorm:"type:json"`
}
func main() {
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// 自动迁移
db.AutoMigrate(&User{})
// 插入数据
db.Create(&User{
Name: "jinzhu",
Attrs: JSON(`{"key": "value"}`),
})
// 查询数据
var user User
db.First(&user)
println(user.Name)
println(string(user.Attrs))
}
有许多第三方包实现了 Scanner
/Valuer
接口,可与 GORM 一起使用,例如:
import (
"github.com/google/uuid"
"github.com/lib/pq"
)
type Post struct {
ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"`
Title string
Tags pq.StringArray `gorm:"type:text[]"`
}
GormDataTypeInterface
GORM 会从 type
标签 中读取字段的数据库类型,如果找不到,则会检查该结构体是否实现了 GormDBDataTypeInterface
或 GormDataTypeInterface
接口,然后使用接口返回值作为数据类型
type GormDataTypeInterface interface {
GormDataType() string
}
type GormDBDataTypeInterface interface {
GormDBDataType(*gorm.DB, *schema.Field) string
}
GormDataType
的结果用于生成通用数据类型,也可以通过 schema.Field
的 DataType
字段得到。这在 编写插件 或者 hook 时可能会有用,例如:
func (JSON) GormDataType() string {
return "json"
}
type User struct {
Attrs JSON
}
func (user User) BeforeCreate(tx *gorm.DB) {
field := tx.Statement.Schema.LookUpField("Attrs")
if field.DataType == "json" {
// 做点什么...
}
}
在迁移时,GormDBDataType
通常会为当前驱动返回恰当的数据类型,例如:
func (JSON) GormDBDataType(db *gorm.DB, field *schema.Field) string {
// 使用 field.Tag、field.TagSettings 获取字段的 tag
// 查看 https://github.com/go-gorm/gorm/blob/master/schema/field.go 获取全部的选项
// 根据不同的数据库驱动返回不同的数据类型
switch db.Dialector.Name() {
case "mysql", "sqlite":
return "JSON"
case "postgres":
return "JSONB"
}
return ""
}
如果 struct 没有实现 GormDBDataTypeInterface
或 GormDataTypeInterface
接口,GORM 会根据 struct 第一个字段推测其数据类型,例如:会为 NullString
使用 string
type NullString struct {
String string // 使用第一个字段的数据类型
Valid bool
}
type User struct {
Name NullString // 数据类型会是 string
}
GormValuerInterface
GORM 提供了 GormValuerInterface
接口,支持使用 SQL 表达式或基于 context 的值进行 create/update,例如:
// GORM Valuer interface
type GormValuerInterface interface {
GormValue(ctx context.Context, db *gorm.DB) clause.Expr
}
使用 SQL 表达式进行 Create/Update
type Location struct {
X, Y int
}
func (loc Location) GormDataType() string {
return "geometry"
}
func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
return clause.Expr{
SQL: "ST_PointFromText(?)",
Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
}
}
// Scan 方法实现了 sql.Scanner 接口
func (loc *Location) Scan(v interface{}) error {
// Scan a value into struct from database driver
}
type User struct {
ID int
Name string
Location Location
}
db.Create(&User{
Name: "jinzhu",
Location: Location{X: 100, Y: 100},
})
// INSERT INTO `users` (`name`,`point`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))
db.Model(&User{ID: 1}).Updates(User{
Name: "jinzhu",
Point: Point{X: 100, Y: 100},
})
// UPDATE `user_with_points` SET `name`="jinzhu",`point`=ST_PointFromText("POINT(100 100)") WHERE `id` = 1
你也可以根据 SQL 表达式进行 create/update,查看 Create From SQL Expr 和 Update with SQL Expression 获取详情
基于 Context 的值
如果你想创建或更新一个依赖于当前 context 的值,你也可以实现 GormValuerInterface
接口,例如:
type EncryptedString struct {
Value string
}
func (es EncryptedString) GormValue(ctx context.Context, db *gorm.DB) (expr clause.Expr) {
if encryptionKey, ok := ctx.Value("TenantEncryptionKey").(string); ok {
return clause.Expr{SQL: "?", Vars: []interface{}{Encrypt(es.Value, encryptionKey)}}
} else {
db.AddError(errors.New("invalid encryption key"))
}
return
}
条件表达式
如果你想构建一些查询 helper,你可以让 struct 实现 clause.Expression
接口:
type Expression interface {
Build(builder Builder)
}
查看 JSON 和 SQL Builder 获取详情,下面是一个示例:
// 根据 Clause Expression 生成 SQL
db.Find(&user, datatypes.JSONQuery("attributes").HasKey("role"))
db.Find(&user, datatypes.JSONQuery("attributes").HasKey("orgs", "orga"))
// MySQL
// SELECT * FROM `users` WHERE JSON_EXTRACT(`attributes`, '$.role') IS NOT NULL
// SELECT * FROM `users` WHERE JSON_EXTRACT(`attributes`, '$.orgs.orga') IS NOT NULL
// PostgreSQL
// SELECT * FROM "user" WHERE "attributes"::jsonb ? 'role'
// SELECT * FROM "user" WHERE "attributes"::jsonb -> 'orgs' ? 'orga'
db.Find(&user, datatypes.JSONQuery("attributes").Equals("jinzhu", "name"))
// MySQL
// SELECT * FROM `user` WHERE JSON_EXTRACT(`attributes`, '$.name') = "jinzhu"
// PostgreSQL
// SELECT * FROM "user" WHERE json_extract_path_text("attributes"::json,'name') = 'jinzhu'