gorm子句构造器Clause的理解和使用
- 一、说明
- 二、初始化clause的过程和方式
- 三、自定义子句生成器
- 1. 使用DB.Clauses方法
- 1.1 传入clause.Interface类型
- 1.2 传入Expression类型
- 1.3 传入StatementModifier类型
- 2. 为单个字段实现开发构造器
- 2.1 实现方法
- 2.2 示例:软删除字段的优化
一、说明
gorm与子句生成器有关的类,按父级到子集排列为 DB --> Statement --> Clause --> Expression ,它们都是以属性形式保存在父类中。
在实际操作用,每次使用Find
、First
这些写方法时,都会生成一个Statement对象,后面就是对Statement中的Clauses属性进行添加、修改和执行,执行过程中调用Expression接口的表达式生成器,组装sql语句。
二、初始化clause的过程和方式
-
在使用gorm的
Find
、First
这些写方法时,通过参数传入。会将clause添加到Statement中。// Find find records that match given conditions func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB) {tx = db.getInstance()if len(conds) > 0 {if exprs := tx.Statement.BuildCondition(conds[0], conds[1:]...); len(exprs) > 0 {tx.Statement.AddClause(clause.Where{Exprs: exprs})}}tx.Statement.Dest = destreturn tx.callbacks.Query().Execute(tx) }
-
上面的代码
return tx.callbacks.Query().Execute(tx)
执行时,会添加gorm默认的Clause到Statement中
https://github.com/go-gorm/gorm/blob/master/callbacks.go
关键部分代码:if len(stmt.BuildClauses) == 0 {stmt.BuildClauses = p.ClausesresetBuildClauses = true }
-
使用
DB.Clauses
方法时,可以将自定义的子句生成器添加到Statement中。重点是传入的参数需要实现StatementModifier接口中的方法。// Clauses Add clauses func (db *DB) Clauses(conds ...clause.Expression) (tx *DB) {tx = db.getInstance()var whereConds []interface{}for _, cond := range conds {if c, ok := cond.(clause.Interface); ok {tx.Statement.AddClause(c)} else if optimizer, ok := cond.(StatementModifier); ok {optimizer.ModifyStatement(tx.Statement)} else {whereConds = append(whereConds, cond)}}if len(whereConds) > 0 {tx.Statement.AddClause(clause.Where{Exprs: tx.Statement.BuildCondition(whereConds[0], whereConds[1:]...)})}return }
通过代码可以看到,支持3种类型的参数参入:
clause.Interface
子句生成器接口StatementModifier
修改staement的接口clause.Expression
表达式生成器接口,这里的表达式最终会添加到clause.Where中,也就是把原先的clause.Where覆盖掉。
-
针对模型字段,可以按照以下接口为其添加子句生成器。
//调用gorm的创建方法时生效 type CreateClausesInterface interface {CreateClauses(*Field) []clause.Interface } //调用gorm的查询方法时生效 type QueryClausesInterface interface {QueryClauses(*Field) []clause.Interface } //调用gorm的更新方法时生效 type UpdateClausesInterface interface {UpdateClauses(*Field) []clause.Interface } //调用gorm的删除方法时生效 type DeleteClausesInterface interface {DeleteClauses(*Field) []clause.Interface }
这些方法它只针对携带这个方法的字段生效。如gorm.DeletedAt字段,会自动添加查询条件
deleted_at IS NULL
。
三、自定义子句生成器
gorm本身已经定义了很多的子句构造器,除了本文中的示例,我们也可以直接参考源码来实现自己的子句构造器
1. 使用DB.Clauses方法
1.1 传入clause.Interface类型
通过db.Clauses()
方法,可以对gorm的SQL语句做一些拓展。clause.Interface是clause的接口。而传入的参数需要实现接口中的方法。
我们来看一下网上引用比较多的gorm自带的子句生成器clause.OnConflict{},它实现的是clause.Interface
这个接口,也就是子句生成器。
// 使用SQL语句
db.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "id"}},DoUpdates: clause.Assignments(map[string]interface{}{"count": gorm.Expr("GREATEST(count, VALUES(count))")}),
}).Create(&users)
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `count`=GREATEST(count, VALUES(count));
这是使用示例,表示创建时如果id字段重复就会执行DoUpdates中的更新操作。
1.2 传入Expression类型
Expression 是表达式生成器的接口,同时属于clause的子类。比如通过clause.Eq(equal,等值表达式生成器)生成WHERE id=100
。
重写表达式可能会导致的问题
重写的表达式生成器在特定的场景下,可能会拿不到字段名(拿到nil
)。这时候有两种方法,一种是做判断跳过。还有一种就是从父类开始重写。
1.3 传入StatementModifier类型
StatementModifier,修改staement的方法。
2. 为单个字段实现开发构造器
2.1 实现方法
只要自定义的模型字段中包含以下的其中一种方法即可,
CreateClauses(*Field) []clause.Interface
QueryClauses(*Field) []clause.Interface
UpdateClauses(*Field) []clause.Interface
DeleteClauses(*Field) []clause.Interface
这个返回值类型可以实现StatementModifier接口 或者 clause.Interface接口都行。相关的实现细节可以参考源码的软删除字段gorm.DeletedAt字段。
2.2 示例:软删除字段的优化
gorm包本身带有一个软删除字段gorm.DeletedAt,它具有一个与本文有关的功能点是,在生成SQL查询语句时,gorm会自动调用其自带的QueryClauses
函数,添加查询条件deleted_at IS NULL
。
而在MYSQL中,这个查询条件有一个短板——耗时。
起初可能没什么感觉,只要数据量过10万,查询速度会很明显的变慢。我想把它自动生成的查询条件改为IFNULL(deleted_at IS NULL,0)=0
,IFNULL这个方法可以让查询速度几何倍提升。
接下来,我们需要重写QueryClauses
方法,示例如下:
import ("gorm.io/gorm""gorm.io/gorm/clause""gorm.io/gorm/schema"
)//DeletedAt 继承gorm.DeletedAt
type DeletedAt struct {gorm.DeletedAt
}
//重写QueryClauses
func (DeletedAt) QueryClauses(f *schema.Field) []clause.Interface {return []clause.Interface{BeautifulSoftDeleteQueryClause{Field: f}}
}
QueryClauses
实际是返回了 StatementModifier接口的实例,我们继续重写这个类:
type BeautifulSoftDeleteQueryClause struct {gorm.SoftDeleteQueryClauseField *schema.Field
}func (b BeautifulIsNULLSearch) ModifyStatement(stmt *gorm.Statement) {if _, ok := stmt.Clauses["soft_delete_enabled"]; !ok {if c, ok := stmt.Clauses["WHERE"]; ok {if where, ok := c.Expression.(clause.Where); ok && len(where.Exprs) > 1 {for _, expr := range where.Exprs {if orCond, ok := expr.(clause.OrConditions); ok && len(orCond.Exprs) == 1 {where.Exprs = []clause.Expression{clause.And(where.Exprs...)}c.Expression = wherestmt.Clauses["WHERE"] = cbreak}}}}stmt.AddClause(clause.Where{Exprs: []clause.Expression{//调用自定义表达式生成器Eq{Column: clause.Column{Table: clause.CurrentTable, Name: b.Field.DBName}, Value: nil},}})stmt.Clauses["soft_delete_enabled"] = clause.Clause{}}
}
ModifyStatement
方法调用了表达式生成器Eq
(equal),真正生成查询条件的地方也就是这里,我们继续重写:
// Eq equal to for where
type Eq struct {Column interface{}Value interface{}clauseName string
}func (eq Eq) Build(builder clause.Builder) {switch eq.Value.(type) {case []string, []int, []int32, []int64, []uint, []uint32, []uint64, []interface{}:builder.WriteQuoted(builder)builder.WriteString(" IN (")rv := reflect.ValueOf(eq.Value)for i := 0; i < rv.Len(); i++ {if i > 0 {builder.WriteByte(',')}builder.AddVar(builder, rv.Index(i).Interface())}builder.WriteByte(')')default:column, _ := eq.Column.(clause.Column)stmt, _ := builder.(*gorm.Statement)if eqNil(eq.Value) {// 重写原生方法 builder.WriteString(" IS NULL")builder.WriteString(fmt.Sprintf("IFNULL(`%s`.%s, 0) = 0", stmt.Table, column.Name))} else {builder.WriteString(fmt.Sprintf("`%s`.%s = ", stmt.Table))builder.AddVar(builder, eq.Value)}}
}func (eq Eq) NegationBuild(builder clause.Builder) {Neq(eq).Build(builder)
}func eqNil(value interface{}) bool {if valuer, ok := value.(driver.Valuer); ok && !eqNilReflect(valuer) {value, _ = valuer.Value()}return value == nil || eqNilReflect(value)
}
func eqNilReflect(value interface{}) bool {reflectValue := reflect.ValueOf(value)return reflectValue.Kind() == reflect.Ptr && reflectValue.IsNil()
}
效果
数据表模型如下
type MODEL struct {ID uint `gorm:"primarykey"` // 主键IDCreatedAt time.Time // 创建时间UpdatedAt time.Time // 更新时间DeletedAt DeletedAt `gorm:"index" json:"-"` // 删除时间
}
type User struct{MODELName string
}
func (User)TableName()string{return "sys_user"
}
查询示例
var user []User
DB.Find(&user)
// SELECT * FROM `sys_user` WHERE IFNULL(`sys_user`.deleted_at, 0) = 0