c
package model
import "time"
// Template 模板主表
type Template struct {
ID int64 `xorm:"pk autoincr"`
Name string `xorm:"varchar(64)"`
// 第一类策略,业务组装字段,不存库
StrategyIds []int64 `xorm:"-"`
Strategys []*Strategy `xorm:"-"`
// 第二类策略,业务组装字段,不存库
Strategy1Ids []int64 `xorm:"-"`
Strategys1 []*Strategy1 `xorm:"-"`
}
// Task 任务(多对一模板,从表)
type Task struct {
ID int64 `xorm:"pk autoincr"`
TaskName string `xorm:"varchar(64)"`
TemplateID int64 `xorm:"index"`
Template *Template `xorm:"-"`
CreateTime time.Time `xorm:"created"`
}
// Strategy 第一种策略主表
type Strategy struct {
ID int64 `xorm:"pk autoincr"`
Name string `xorm:"varchar(64)"`
}
// Strategy1 第二种策略主表
type Strategy1 struct {
ID int64 `xorm:"pk autoincr"`
Name string `xorm:"varchar(64)"`
}
// TemplateStrategy 模板<->Strategy 中间多对多表
type TemplateStrategy struct {
TemplateID int64 `xorm:"pk"`
StrategyID int64 `xorm:"pk"`
}
// TemplateStrategy1 模板<->Strategy1 中间多对多表
type TemplateStrategy1 struct {
TemplateID int64 `xorm:"pk"`
Strategy1ID int64 `xorm:"pk"`
}
一、问 :StrategyIds \[\]int64 xorm:"-"已经存储多个策略的id,为什么还要建立中间表
答案:
核心一句话:
StrategyIds []int64 只用于「前后端传参、接口出参」,不落地数据库;数据库存关联必须中间表,不能靠数组字段存关联。
1、两个字段分工完全不一样
go
StrategyIds []int64 `xorm:"-"` // 不在数据库建字段!!
Strategys []*Strategy `xorm:"-"`
xorm:"-"→ xorm同步建表忽略此字段,MySQL表里没有这一列- 作用:前端传
[1,2,3]接收、查询完组装ID列表返回前端,不持久化存储关联关系
想把策略ID数组存库只有两种:JSON字段 / 中间表
- JSON:不能外键、不能连表JOIN、筛选困难
- 中间表:标准数据库范式,用来持久保存【模板-策略】绑定关系
2、如果去掉中间表、只用数据库JSON存ID数组的痛点
- 没法用
join template inner join strategy联表查询 - 根据策略ID反向查所有绑定的模板:
where JSON_CONTAINS(ids,?);不走索引,大数据慢 - 删策略时数据库无法外键级联删除,产生脏数据
- 不方便SQL统计:一个策略被多少模板引用
3、业务完整流程(帮你理清逻辑)
- 前端提交:
strategyIds:[1,2]→ 赋值给Template.StrategyIds - 后端代码:删掉中间表旧数据 → 循环批量插入TemplateStrategy(持久绑定关系)
- 查询:查中间表拿到所有策略ID → 塞回
Template.StrategyIds、Template.Strategys返回前端
精简总结
StrategyIds[]int64(xorm:"-"):接口临时载体,不存库- TemplateStrategy中间表:数据库持久保存多对多绑定关系,必建
二、给出go操作xorm写入中间表的代码
一、已有model结构体(不变)
go
package model
import "time"
// Template 模板
type Template struct {
ID int64 `xorm:"pk autoincr"`
Name string `xorm:"varchar(64)"`
StrategyIds []int64 `xorm:"-"`
Strategys []*Strategy `xorm:"-"`
Strategy1Ids []int64 `xorm:"-"`
Strategys1 []*Strategy1 `xorm:"-"`
}
// Task 任务
type Task struct {
ID int64 `xorm:"pk autoincr"`
TaskName string `xorm:"varchar(64)"`
TemplateID int64 `xorm:"index"`
Template *Template `xorm:"-"`
CreateTime time.Time `xorm:"created"`
}
// Strategy 第一类策略
type Strategy struct {
ID int64 `xorm:"pk autoincr"`
Name string `xorm:"varchar(64)"`
}
// Strategy1 第二类策略
type Strategy1 struct {
ID int64 `xorm:"pk autoincr"`
Name string `xorm:"varchar(64)"`
}
// TemplateStrategy 模板-策略中间表
type TemplateStrategy struct {
TemplateID int64 `xorm:"pk"`
StrategyID int64 `xorm:"pk"`
}
// TemplateStrategy1 模板-策略1中间表
type TemplateStrategy1 struct {
TemplateID int64 `xorm:"pk"`
Strategy1ID int64 `xorm:"pk"`
}
二、保存模板+同步写入两张中间表代码
go
package main
import (
"xorm.io/xorm"
"yourpath/model"
)
// SaveTemplate 新增/修改模板并维护两张中间关联表
// temp:前端传来的模板数据(包含StrategyIds、Strategy1Ids数组)
func SaveTemplate(engine *xorm.Engine, temp *model.Template) (int64, error) {
var err error
// 1.新增/更新模板主表
if temp.ID == 0 {
temp.ID, err = engine.Insert(temp)
if err != nil {
return 0, err
}
} else {
_, err = engine.ID(temp.ID).Update(temp)
if err != nil {
return 0, err
}
}
// ========== 处理第一张中间表 TemplateStrategy ==========
// 先删除该模板原有绑定
_, err = engine.Where("template_id = ?", temp.ID).Delete(&model.TemplateStrategy{})
if err != nil {
return 0, err
}
// 拼装中间表数据批量插入
var tsList []model.TemplateStrategy
for _, sid := range temp.StrategyIds {
tsList = append(tsList, model.TemplateStrategy{
TemplateID: temp.ID,
StrategyID: sid,
})
}
if len(tsList) > 0 {
_, err = engine.Insert(tsList)
if err != nil {
return 0, err
}
}
// ========== 处理第二张中间表 TemplateStrategy1 ==========
_, err = engine.Where("template_id = ?", temp.ID).Delete(&model.TemplateStrategy1{})
if err != nil {
return 0, err
}
var ts1List []model.TemplateStrategy1
for _, sid := range temp.Strategy1Ids {
ts1List = append(ts1List, model.TemplateStrategy1{
TemplateID: temp.ID,
Strategy1ID: sid,
})
}
if len(ts1List) > 0 {
_, err = engine.Insert(ts1List)
}
return temp.ID, err
}
三、查询回填数组字段配套代码(按需使用)
go
// FillTemplateRel 查中间表,回填StrategyIds、Strategy1Ids、策略详情
func FillTemplateRel(engine *xorm.Engine, temp *model.Template) error {
// 填充第一类策略ID和详情
var ts []model.TemplateStrategy
err := engine.Where("template_id=?", temp.ID).Find(&ts)
if err != nil {
return err
}
var sIds []int64
for _, item := range ts {
sIds = append(sIds, item.StrategyID)
}
temp.StrategyIds = sIds
if len(sIds) > 0 {
engine.In("id", sIds).Find(&temp.Strategys)
}
// 填充第二类策略ID和详情
var ts1 []model.TemplateStrategy1
err = engine.Where("template_id=?", temp.ID).Find(&ts1)
if err != nil {
return err
}
var s1Ids []int64
for _, item := range ts1 {
s1Ids = append(s1Ids, item.Strategy1ID)
}
temp.Strategy1Ids = s1Ids
if len(s1Ids) > 0 {
engine.In("id", s1Ids).Find(&temp.Strategys1)
}
return nil
}
调用示例
go
func demo(engine *xorm.Engine) {
// 前端入参
t := &model.Template{
Name: "测试模板",
StrategyIds: []int64{1, 2, 3}, // 第一类策略id数组
Strategy1Ids: []int64{5, 6}, // 第二类策略id数组
}
// 保存主表+中间表
_, _ = SaveTemplate(engine, t)
// 查询模板并自动回填策略数组
var getT model.Template
engine.ID(t.ID).Get(&getT)
_ = FillTemplateRel(engine, &getT)
}
逻辑说明
- 删旧存新:每次更新先删当前模板所有历史关联,再批量新增,避免冗余数据;
- 中间表存储:一个模板+一个策略 = 一行记录,多条实现数组绑定;
xorm:"-"字段只做前后端交互,不落地数据库。
三、问 中间表没有自己的id么?
两种中间表写法,按需选用
方式1:当前复合主键(无单独自增ID,你现在代码)
go
// 联合主键,没有单独id字段
type TemplateStrategy struct {
TemplateID int64 `xorm:"pk"`
StrategyID int64 `xorm:"pk"`
}
- 主键 =
(template_id,strategy_id),天然去重,不能重复绑定同一策略 - 优点:少字段、省空间、多对多标准设计
- 缺点:没法通过中间表ID单独删除某一条绑定
方式2:加独立自增ID(常用,推荐改这个)
go
// 新增自增主键ID,两个外键单独索引
type TemplateStrategy struct {
ID int64 `xorm:"pk autoincr"` // 中间表自身主键
TemplateID int64 `xorm:"index"`
StrategyID int64 `xorm:"index"`
}
type TemplateStrategy1 struct {
ID int64 `xorm:"pk autoincr"`
TemplateID int64 `xorm:"index"`
Strategy1ID int64 `xorm:"index"`
}
特点:
- 每行记录有唯一
id,可根据中间表ID单条删除 TemplateID、StrategyID只是普通外键索引,不再做主键- 如需防重复绑定,加唯一联合索引标签:
go
TemplateID int64 `xorm:"index(uniq_tpl_str)"`
StrategyID int64 `xorm:"index(uniq_tpl_str)"`
保存中间表代码不用改动
删的时候依旧:
go
engine.Where("template_id=?",tid).Delete(&model.TemplateStrategy{})
总结
- 不需要单条精准删关联 → 复合主键无ID(原有写法)
- 后续可能根据中间表主键操作 → 加ID自增字段(方案2)
四、采用第一种方法怎么增删改查
方案:中间表【复合主键、无自增ID】增删改查完整Xorm代码
go
// 中间表结构(沿用你原来复合主键)
type TemplateStrategy struct {
TemplateID int64 `xorm:"pk"`
StrategyID int64 `xorm:"pk"`
}
type TemplateStrategy1 struct {
TemplateID int64 `xorm:"pk"`
Strategy1ID int64 `xorm:"pk"`
}
复合主键规则:
TemplateID+StrategyID唯一,同模板不能重复绑同一个策略
1、新增(批量新增,前端传ID数组)
go
// 给模板绑定策略,先删全量旧数据再批量新增(标准改绑逻辑)
func BindStrategy(engine *xorm.Engine, templateId int64, sIds []int64) error {
// 1.删除该模板全部旧关联
_, err := engine.Where("template_id=?", templateId).Delete(new(model.TemplateStrategy))
if err != nil {
return err
}
// 2.组装切片批量insert
var list []model.TemplateStrategy
for _, sid := range sIds {
list = append(list, model.TemplateStrategy{
TemplateID: templateId,
StrategyID: sid,
})
}
if len(list) > 0 {
_, err = engine.Insert(&list)
}
return err
}
单个新增一条:
engine.Insert(&model.TemplateStrategy{TemplateID:1,StrategyID:2})
2、删除
① 删除某个模板全部关联(最常用)
go
// 删除模板=tid所有绑定
engine.Where("template_id=?", tid).Delete(new(model.TemplateStrategy))
② 删除【指定模板+指定策略】单条
go
// 复合主键精准删一条
_, err := engine.Where("template_id=? and strategy_id=?", tid, sid).Delete(new(model.TemplateStrategy))
3、修改
复合主键没有单条Update用法:多对多常规逻辑:删旧全量→插新数据
没有update单字段,改绑统一:
- 删掉当前模板所有中间表数据
- 插入新的ID数组,上面BindStrategy就是修改逻辑
场景:模板原来绑1,2 → 修改为2,3:先删全部,再插入2、3
4、查询
① 根据模板ID查所有绑定策略ID
go
func GetBindSids(engine *xorm.Engine, tid int64) ([]int64, error) {
var rel []model.TemplateStrategy
err := engine.Where("template_id=?", tid).Find(&rel)
if err != nil {
return nil, err
}
var sids []int64
for _, v := range rel {
sids = append(sids, v.StrategyID)
}
return sids, nil
}
② 根据策略ID反向查所有关联模板ID
go
engine.Where("strategy_id=?", sid).Find(&rel)
③ 判断某个策略是否已绑定该模板
go
has, _ := engine.Where("template_id=? and strategy_id=?", tid, sid).Exist(new(model.TemplateStrategy))
第二类策略TemplateStrategy1
代码逻辑完全一模一样,只替换结构体名即可。
补充:保存模板整体调用示例
go
// 前端传入模板
tpl := &model.Template{
Name: "模板A",
StrategyIds: []int64{1,3},
Strategy1Ids: []int64{2,5},
}
// 1.保存主表
tid,_ := engine.Insert(tpl)
// 2.绑定两类策略
BindStrategy(engine, tid, tpl.StrategyIds)
BindStrategy1(engine, tid, tpl.Strategy1Ids)
五、go语言使用gorm中是怎么操作关联表的?一对多,多对一,多对多
GORM 关联表操作(一对多、多对一、多对多)
GORM v2 版本,基于标签
gorm:"foreignKey,references"绑定外键,分模型定义、增删改查三部分
一、多对一(N:1):多个子表 → 一个父表
示例:订单Order(N) → 用户User(1),多个订单归属同一个用户
1. 模型定义
go
type User struct {
gorm.Model
Name string
}
type Order struct {
gorm.Model
UserID uint // 外键:关联User.ID(默认规则:父表名+ID)
User User // 多对一关联字段
Amount float64
}
UserID:外键字段(必须存在)User User:结构体嵌套,用于预加载查询
2. CRUD
go
// 1.新增订单(绑定用户)
db.Create(&Order{UserID:1, Amount:99})
// 2.查询订单顺带查所属用户(Preload预加载)
var order Order
db.Preload("User").First(&order,1)
// 3.修改所属用户
db.Model(&order).Update("UserID",2)
二、一对多(1:N):一个父表 → 多个子表
承接上面:User(1) → Order(N),一个用户多个订单
1.模型定义
go
type User struct {
gorm.Model
Name string
Orders []Order // 一对多切片字段
}
type Order struct {
gorm.Model
UserID uint
Amount float64
}
- 外键依旧在多方
Order.UserID,一方用切片存储关联数据
2.CRUD
go
// 1.创建用户+同时批量创建订单(事务自动保存关联)
user := User{
Name:"张三",
Orders:[]Order{{Amount:100},{Amount:200}},
}
db.Create(&user) // 自动填充Order.UserID=user.ID
// 2.查询用户+预加载所有订单
var u User
db.Preload("Orders").First(&u,1)
// 3.给用户新增关联订单
db.Model(&u).Association("Orders").Append(&Order{Amount:300})
// 4.移除某个订单关联(仅清外键,不删订单数据)
db.Model(&u).Association("Orders").Delete(&order)
Association("关联名"):GORM专用关联操作API(追加/删除/清空/替换)
三、多对多(M:N):两张表互相多关联,自动生成中间表
示例:商品Product ↔ 分类Category,一个商品多个分类,一个分类多个商品
GORM自动生成中间表 (默认表名:product_categories),无需手动建中间表模型
1.模型定义
go
type Product struct {
gorm.Model
Name string
Categories []Category `gorm:"many2many:product_categories;"` // 指定中间表名
}
type Category struct {
gorm.Model
Name string
Products []Product `gorm:"many2many:product_categories;"`
}
many2many:中间表名:必填标签,两边必须一致
2.CRUD
go
// 1.新建商品+绑定多个分类
p := Product{
Name:"手机",
Categories:[]Category{{Name:"数码"},{Name:"3C"}},
}
db.Create(&p) // 自动维护中间表数据
// 2.查询商品+预加载所有分类
var prod Product
db.Preload("Categories").First(&prod,1)
// 3.追加新分类关联
cat := Category{Name:"电子产品"}
db.Model(&prod).Association("Categories").Append(&cat)
// 4.移除某个分类关联(只删中间表记录,不删商品/分类主表)
db.Model(&prod).Association("Categories").Delete(&cat)
// 5.清空所有关联
db.Model(&prod).Association("Categories").Clear()
四、常用预加载优化
- 嵌套预加载(关联里再查关联)
go
// 用户→订单,订单里如需扩展关联地址
db.Preload("Orders.Address").First(&user)
- 条件预加载
go
// 只查询金额>50的订单
db.Preload("Orders",func(tx *gorm.DB)*gorm.DB{
return tx.Where("amount > ?",50)
}).First(&user)
五、自定义外键/关联名(非常规字段)
不想用默认UserID,自定义外键:
go
type Order struct {
gorm.Model
Uid uint `gorm:"foreignKey:Uid;references:UID"` // Uid=本字段,UID=User主键字段
User User
}
type User struct {
UID uint `gorm:"primaryKey"` // 自定义主键
Name string
}
需要我把关联删除(级联删除)、软删除关联写法补充一份示例吗?