beego ORM多对多查询必须用QueryM2M,RelatedSel()不支持;QueryM2M需传主模型指针和字段名字符串,All()参数必须是指针切片,ValuesList()用中间表字段名,复杂条件需Raw()配合。

beego ORM 多对多查询必须用 QueryM2M,不能靠 RelatedSel()
beego 的 RelatedSel() 只支持一对多、一对一的正向/反向关联,对多对多关系完全无效。如果你在模型里定义了 Users []*User `orm:"rel(m2m)"`,却还写 o.QueryTable("article").Filter("id", 1).RelatedSel().All(&a),结果里的 a.Users 一定是空的——ORM 根本不会去查中间表。
真正起作用的是 QueryM2M,它返回一个专门操作中间关系的句柄,后续所有增删查都得通过它。
-
QueryM2M第一个参数必须是已存在的主模型实例(不能是新 struct、不能是 nil),比如&article;第二个参数是结构体中多对多字段名字符串,如"Users" - 中间表名默认是
model1_model2(按字母序),例如article_user;若自定义了,需在字段标签里加orm:"rel(m2m);rel_table(article_users)" - 调用
QueryM2M不会触发 SQL 查询,只是准备上下文;真正查数据要调用.All()或.Exist()
QueryM2M.All() 查出关联对象列表,但要注意指针类型和内存分配
查用户文章的全部作者,典型写法是:
article := models.Article{ID: 123}
err := o.Read(&article) // 先加载主对象
if err != nil { return }
m2m := o.QueryM2M(&article, "Authors")
var authors []*models.Author
_, err = m2m.All(&authors)
关键点:
-
All()的参数必须是指针切片(*[]*Author),不是值切片([]Author)或单个指针(*Author),否则 panic 或静默失败 - 如果
Authors字段定义为[]*Author,那All()就必须传&authors;如果定义为[]Author(不推荐),则传&authors后还要手动转成指针切片 -
All()不会填充主对象的Authors字段,它只负责把结果写进你传入的变量,主对象仍是“干净”的
查中间表 ID 列表比查完整对象更轻量,适合分页或仅需 ID 场景
如果只是要文章关联的用户 ID 列表(比如做缓存 key、做二次筛选),直接用 .ValuesList() 比 .All() 快得多,避免无谓的 JOIN 和结构体反射:
m2m := o.QueryM2M(&article, "Users")
var userIds []int64
_, err := m2m.ValuesList("user_id", &userIds) // 注意字段名是中间表字段,不是模型字段
注意:
-
ValuesList()第一个参数是中间表的列名(如"user_id"、"article_id"),不是目标模型的字段名(如"Id") - 中间表字段名默认是
model1_id和model2_id,顺序按模型注册时的字母序,不是按你在结构体里写的顺序 - 返回的
userIds是[]int64,即使你的主键是int类型,ORM 也统一转成int64,别用[]int接收
多对多条件查询只能靠 Raw(),Filter() 在中间表上基本没用
想查“被至少 3 个 VIP 用户收藏的文章”,或者“作者 A 和作者 B 共同撰写的文档”,这类带聚合或跨关联条件的查询,QueryM2M 本身不支持 Filter() 链式调用,也不能嵌套子查询。必须退到原生 SQL:
sql := `SELECT a.* FROM article a
INNER JOIN article_user au ON a.id = au.article_id
WHERE au.user_id IN (?, ?)
GROUP BY a.id HAVING COUNT(DISTINCT au.user_id) = 2`
var articles []models.Article
o.Raw(sql, userID1, userID2).QueryRows(&articles)
这里容易踩的坑:
- 别在
Raw()里用QueryM2M返回的中间表名变量——它只是内部句柄,不暴露表名;中间表名要么硬编码,要么在模型里用rel_table显式声明后统一维护 - 聚合查询(
GROUP BY、HAVING)必须搭配QueryRows(),All()会按单条记录映射,结果错乱 - 如果仍想走 ORM 结构体映射,建议先用
Raw()查 ID 列表,再用QueryTable("article").Filter("id__in", idList).All()加载完整对象,两次查询但语义清晰、不易出错
多对多的难点不在语法,而在于中间表的生命周期管理:插入、删除、批量同步都得自己控制 QueryM2M 句柄,且没有事务自动包裹。哪怕只漏掉一次 o.Begin() / o.Commit(),就可能让中间表和主表状态不一致。


















