gorm子句构造器Clause的理解和使用

news/2024/4/20 21:21:49/文章来源:https://blog.csdn.net/dorlolo/article/details/127238144

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 ,它们都是以属性形式保存在父类中。

在实际操作用,每次使用FindFirst这些写方法时,都会生成一个Statement对象,后面就是对Statement中的Clauses属性进行添加、修改和执行,执行过程中调用Expression接口的表达式生成器,组装sql语句。

二、初始化clause的过程和方式

  1. 在使用gorm的FindFirst这些写方法时,通过参数传入。会将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)
    }
    
  2. 上面的代码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
    }
    
  3. 使用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覆盖掉
  4. 针对模型字段,可以按照以下接口为其添加子句生成器。

    //调用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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_398833.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ORACLE新增数据库(用户),使用navicate

oracle新增数据库并不像mysql直接指令就行&#xff0c;百度一圈都是用Oracle Database Configuration Assistant的&#xff0c;其实navicate就直接可以新建&#xff0c;以下是新建方法&#xff1a; 1.连接数据库 2.新建表空间 点击navicate上方工具栏中"其它"&…

何为功能平价?特斯拉「抛弃」多传感融合,背后有哪些门道

技术与成本&#xff0c;永远是博弈的两方。 当大部分车企都在寻求通过增加更多、更高性能的传感器&#xff08;也就是通常所说的多传感融合技术&#xff09;来强化智能驾驶功能可靠性和拓展性的大背景下&#xff0c;特斯拉依然我行我素&#xff0c;继续沿着纯视觉感知的路线前…

盘点一个Python列表(元素多样)处理的实战题目(使用正则表达式也可以实现)

大家好,我是Python进阶者。 一、前言 前几天在Python白银交流群【凡人不烦人】问了一个Python列表处理的问题,提问截图如下:下面是他的部分数据: lst = [(问答题)(2) 假设镀锌钢管, http://admintk.sc.zzstep.com/UpLoadImage/2019-10-10/a84f340e-6c67-42b1-8eae-3dc14281…

队列的操作实验(数据结构)

队列的操作实验&#xff08;数据结构&#xff09; 一、实验目的 1&#xff0e;掌握队列存储结构的表示和实现方法。 2&#xff0e;掌握队列的入队和出队等基本操作的算法实现。 3&#xff0e;了解队列在解决实际问题中的简单应用。 二、实验内容 1&#xff0e;建立顺序循环队列…

【LeetCode】【二叉搜索树迭代器】

173. 二叉搜索树迭代器 实现一个二叉搜索树迭代器类BSTIterator &#xff0c;表示一个按中序遍历二叉搜索树&#xff08;BST&#xff09;的迭代器&#xff1a; BSTIterator(TreeNode root) 初始化 BSTIterator 类的一个对象。BST 的根节点 root 会作为构造函数的一部分给出。指…

【优化充电】基于matlab粒子群算法电动汽车充电动态优化策略【含Matlab源码 2163期】

一、粒子群算法电动汽车充电优化 1 电动汽车充电负荷估算 电动汽车的充电负荷主要与电动汽车起始充电时刻和充电时长相关,而起始充电时刻是由电动汽车用户的到家时间决定的,充电时长主要与电动汽车的行驶里程和充电倍率相关。 目前电动汽车还没有大规模运营, 只能通过统计燃油…

ASP.NET Core微服务(六)——【.Net Core操作redis】StackExchange.Redis

ASP.NET Core微服务(六)——【.Net Core操作redis】StackExchange.Redis 目录 ASP.NET Core微服务(六)——【.Net Core操作redis】StackExchange.Redis 项目创建 StackExchange.Redis操作示例 引包【using StackExchange.Redis;】 ConnectionMultiplexer RedisDBHelper …

Git学习总结

目录&#xff1a; &#xff08;1&#xff09;版本控制 &#xff08;2&#xff09;Git和SVN的区别 &#xff08;3&#xff09;Git历史 &#xff08;4&#xff09;安装Git及环境配置 &#xff08;5&#xff09;常用的Linux命令 &#xff08;6&#xff09;Git的必要配置 &a…

PMO和PM如何实现从战略解码到项目执行的端到端闭环?

一、PMO的使命与职责 PMO的使命是提升端到端组织效能&#xff0c;赋能于精细化管理&#xff0c;成为企业的加速器&#xff0c;保障战略项目的交付。 那么PMO要保障战略的交付&#xff0c;核心职责有哪些呢&#xff1f; 二、组织为什么需要端到端项目管理&#xff1f; 核心价…

【ZooKeeper】ZooKeeper 应用场景

ZooKeeper 应用场景发布订阅命名服务集群管理分布式锁分布式队列管理负载均衡配置管理ZooKeeper&#xff1a;分布式协调服务&#xff0c;仲裁机构。基于ZNode数据模型和Watcher监听机制可以解决很多问题&#xff0c;比如分布式锁问题。 应用场景如下&#xff1a; 1、发布/订阅 …

servlet基础知识

早期的Web应用主要用于浏览新闻等静态页面&#xff0c;HTTP服务器&#xff08;比如 Apache、Nginx&#xff09;向浏览器返回静态 HTML&#xff0c;浏览器负责解析HTML&#xff0c;将结果呈现给用户。随着互联网的发展&#xff0c;还希望进行一些交互操作来获取动态结果&#xf…

Python Turtle绘图基础(一)——Turtle简介、绘图窗体与绘图区域

今天继续给大家介绍渗透测试相关知识&#xff0c;本文主要内容是Python Turtle绘图基础&#xff0c;包括Turtle简介、绘图窗体与绘图区域。 一、Turtle库简单介绍 Turtle库时Python语言的标准库&#xff08;所谓标准库&#xff0c;就是在安装Python时自带的库&#xff0c;与之…

【经典面试题-LeetCode69/剑指 Offer II 072:x 的平方根 (Python3实现)】

x 的平方根一、题目描述1.题目内容2.样例二、解决方案1.基本代码&#xff08;成功提交&#xff09;2.略微拓展一、题目描述 这是一道经典的面试题&#xff0c;需要我们在不使用任何内置函数的前提下&#xff0c;手动实现求指定整数的算术平方根。 1.题目内容 给你一个非负整数…

Android开发——底部导航栏设计

底部导航栏设计1.依赖配置2.tabbar的UI实现3.tabbar的逻辑绑定4.tabbar的滑动与点击联动其实,常见的Android和微信小程序一样&#xff0c;通常最下面一排需要有一排导航栏&#xff0c;可以通过点击导航栏图标和滑动实现页面跳转&#xff0c;具体实现使用的是Android的 ViewPage…

在MUI框架中对于事件绑定与取消和监听的触发自定义的深入运用与实战

事件绑定 除了使用addEventListener&#xff08;&#xff09;方法侦听特定元素上的事件外&#xff0c;还可以使用。on&#xff08;&#xff09;方法实现批元素的事件绑定。 event Type: String 需监听的事件名称&#xff0c;例如&#xff1a;‘tap’ selector Type: String 选择…

MySQL集群搭建——主从同步(一主二从)

一、安装MySQL数据库 Centos7安装MySQL5.7 目前准备了三台服务器作为主从配置数据库 #主 192.168.159.100:3306 #从 192.168.159.101:3306 #从 192.168.159.102:3306二、修改主数据库配置文件 vim /etc/my.cnf #在mysqld模块中添加如下配置信息 #开启二进制日志 log-binmast…

Win10家庭版利用Hyper-V虚拟机安装Kali Linux

目录 安装Hyper-V 批处理安装 重启电脑 下载Kali镜像 Kali官网下载 Hyper-V虚拟机 创建虚拟机 启动虚拟机 安装Kali 安装前配置 磁盘分区 系统安装 登录系统 近期学习网络安全的相关内容&#xff0c;需要用到很多的安全工具。偶然得知Kali Linux就是专门为网络安…

SD-WAN是面向分支机构的新兴、不断发展的解决方案

在过去的二十年里&#xff0c;人们的工作方式发生了很大变化。共享办公空间、移动性和云现在很常见。业务分散&#xff0c;分支机构得到授权。 当然&#xff0c;这个新功能是一件好事。但是&#xff0c;与此同时&#xff0c;它提出了一个巨大的挑战&#xff1a;多协议标签交换(…

【潮流计算】基于matlab粒子群算法优化电力系统潮流计算【含Matlab源码 2157期】

一、粒子群算法简介 1 标准粒子群优化(PSO)算法 PSO算法根据对环境的适应度将群体中的个体移动到好的区域,将每个个体看作是D维搜索空间中的一个粒子,根据粒子本身的飞行经验和群体中其他同伴的飞行经验调整下一步飞行方向,从而搜索到最好的空间位置解。设第i个粒子的位置表示…

什么是 IoT App SDK?

目录 为什么要开发 IoT App&#xff1f; IoT App SDK 的优势 IoT App SDK 分类 智能生活 App SDK 商用照明 App SDK 智慧社区 App SDK 智慧居住 App SDK 行业 App SDK 其他概念 IoT 设备 通信过程 IoT 云平台 智能面板 名词解释 涂鸦 IoT App SDK 是专为物联网移…