Gorm 预加载及实现联表条件查询仿WhereHas

半腔热情 提交于 2020-05-02 02:28:52

文献参考 http://gorm.book.jasperxu.com/

写Go代码也有快一个月了,最近在将laravel项目转Gin的过程中,遇到了不少因为语法特性而导致迁移问题,其中一个就是Gorm这块

With方法被 Preload ,Association 替代

在laravel中,我们可以通过with方法将关联模型的数据引入并合并到查询的数据结构中

常见的写法如

$builder = Dynamic::query()->with([
    'user:id,nickname,avatar,gender',
    'detail:id,dynamic_id,media_url,cover_img_url,media_time_length',
    //'stat:id,dynamic_id,share_number,like_number,comment_number',
    'topic:id,topic_name,topic_type'
])->where(function($query) use($topicList,$followList){
    $query->whereIn("topic_id", $topicList)
        ->orWhereIn('user_id',$followList);
});

Gorm的Preload和Association方法的作用也是类似于此,主要的区别在于Association服务于主体,Preload可以服务于关联关系的模型
 

db.Model(&user).Association("Languages").Find(&languages)

db.Preload("Orders").Preload("Profile").Preload("Role").Find(&users)
//// SELECT * FROM users;
//// SELECT * FROM orders WHERE user_id IN (1,2,3,4); // has many
//// SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // has one
//// SELECT * FROM roles WHERE id IN (4,5,6); // belongs t

不是很清楚的朋友可以直接跳官方文档查询,实际场景中大家根据需要自行调用即可

本人的理解设计两个方法的原因主要在于,Go的语法中不能存在循环引用

比如我在模型Order中关联了User ,那么我们就无法在User 中关联Order了,因为这样触发了循环引用的原则

但是通过 Preload 和 Association 就能实现主体反转,从而解决了循环嵌套的问题

Gorm中无WhereHas方法

在官方的文档中,我们可以实现模型的 一对一,一对多,多对多的关联

但是却没有在关联关系中添加条件的方法

如果查询条件中只需要一个关联表的数据,实现还是可以比较优雅

//查询当前用户关注的动态列表,并做分页,预加载User,Topic模型的处理
database.DB.Model(brief).Offset(offset).Limit(limit).Where("status = ? and delete_status = ?",dynamic.STATUS_ABLE,dynamic.DELEET_STATUS_DEFAULT).Preload("User").Preload("Topic").Preload("AppTopicDynamicDetail").Related(&list, "follow_dynamic").Find(&list)

但是实际场景中,会有更复杂的情况 , 我除了要查询用户关注的动态,还是把用户关注的话题相关的动态一并查出来

这里可以有两种方案

第一种 ,gorm也支持where条件的嵌套应用,这样使得我们在控制OR条件的时候能有效的控制闭合关系,

但是我们要处理WhereHas的问题还是要用到 原生的where exists,嵌套where的作用并没有很大,我们直接用原生sql ,查询条件的字段中要把表名带上,直接上代码

//注的动态话题动态都要database.DB.Model(dyn).Offset(offset).Limit(limit)
.Where("status = ? and delete_status = ? AND 
(EXISTS(SELECT * FROM app_dynamic_follow WHERE `app_dynamic_follow`.`user_id`  IN (?) AND app_topic_dynamic.id = app_dynamic_follow.`dynamic_id`)
 OR topic_id IN (?)) ", 1, 0 , brief.ID,strings.Join(topicIds,",")
).Preload("User").Preload("Topic").Preload("AppTopicDynamicDetail").Find(&list)

这里也算是把laravel中whereHas的sql翻译过来

Explain执行效率,用上了user_id的索引

第二种,Join 方法,但是灵活性和局限性就没那么高 ,并且也算是脱离了ORM的概念了,基本也是接近引用原生sql了,

database.DB.Table("app_topic_dynamic").Offset(offset).Limit(limit).Where("status = ? and delete_status = ?",dynamic.STATUS_ABLE,dynamic.DELEET_STATUS_DEFAULT).Joins("left join app_dynamic_follow on app_topic_dynamic.id = app_dynamic_follow.`dynamic_id` and (app_topic_dynamic.user_id = ? OR topic_id IN (?))",brief.ID,strings.Join(topicIds,",")).Preload("User").Preload("Topic").Preload("AppTopicDynamicDetail").Find(&list)

 

Explain执行效率,并没有应用索引原因是 Using where; Using join buffer (Block Nested Loop),基本上是最慢的

 

综合所上,,这里更推荐大家用第一种方法,也就是laravel中WhereHas的sql语法 where exists

如有疑惑和描述不准确,欢迎大家给我留言

关键词:go , golang , gin , gorm ,laravel 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!