Skip to content
On this page

关联之一对多

一个女神可以有多个狗哥,一个狗哥只属于一个女神

结构体如下:

Go
type HMDog struct {
	ID          uint
	Name        string
	HMGirlGodID uint // 外键,就是HMGirlGod.ID
}

type HMGirlGod struct {
	ID     uint
	Name   string
	HMDogs []HMDog
}
  • 迁移表
Go
func migrateTable() {
	err := DB.AutoMigrate(&HMGirlGod{}, &HMDog{})
	if err != nil {
		return
	}
}
  • 创建一些数据
Go
func createDGData() {
	hmDog1 := HMDog{Name: "狗哥1号"}
	hmDog2 := HMDog{Name: "狗哥2号"}
	hmDog3 := HMDog{Name: "狗哥3号"}
	hmDog4 := HMDog{Name: "狗哥4号"}
	hmDog5 := HMDog{Name: "狗哥5号"}
	hmDog6 := HMDog{Name: "狗哥6号"}
	hmGirlGod1 := HMGirlGod{
		Name:   "女神1号",
		HMDogs: []HMDog{hmDog1, hmDog2},
	}
	hmGirlGod2 := HMGirlGod{
		Name:   "女神2号",
		HMDogs: []HMDog{hmDog3, hmDog4},
	}
	hmGirlGod3 := HMGirlGod{
		Name:   "女神3号",
		HMDogs: []HMDog{hmDog5, hmDog6},
	}
	hmGirlGods := []HMGirlGod{hmGirlGod1, hmGirlGod2, hmGirlGod3}
	DB.Model(&HMGirlGod{}).Create(&hmGirlGods)
}

HMDog表数据如下

json
[
  {
    "id": 1,
    "name": "狗哥1号",
    "hm_girl_god_id": 1
  },
  {
    "id": 2,
    "name": "狗哥2号",
    "hm_girl_god_id": 1
  },
  {
    "id": 3,
    "name": "狗哥3号",
    "hm_girl_god_id": 2
  },
  {
    "id": 4,
    "name": "狗哥4号",
    "hm_girl_god_id": 2
  },
  {
    "id": 5,
    "name": "狗哥5号",
    "hm_girl_god_id": 3
  },
  {
    "id": 6,
    "name": "狗哥6号",
    "hm_girl_god_id": 3
  }
]

操作数据

Append

先清除一波数据,将女神1号关联的狗哥都清掉

Go
func clearAssociation() {
	// 找出女神1号
	var g1 HMGirlGod
	DB.Model(&HMGirlGod{}).Find(&g1, 1)
	// 清除其关联的所有狗哥
	err := DB.Model(&g1).Association("HMDogs").Clear()
	if err != nil {
		return
	}
}

以上操作后HMDog表数据如下:

json
[
  {
    "id": 1,
    "name": "狗哥1号",
    "hm_girl_god_id": null
  },
  {
    "id": 2,
    "name": "狗哥2号",
    "hm_girl_god_id": null
  },
  ......
]

Append单个

Go
func appendAssociation() {
	// 找出女神1号
	var g1 HMGirlGod
	DB.Model(&HMGirlGod{}).Find(&g1, 1)
	// 找出狗哥1号
	var d1 HMDog
	DB.Model(&HMDog{}).Find(&d1, 1)
	// 将狗哥1号和女神1号关联
	DB.Model(&g1).Association("HMDogs").Append(&d1)
}

以上操作后HMDog表数据如下:

json
[
  {
    "id": 1,
    "name": "狗哥1号",
    "hm_girl_god_id": 1
  },
  {
    "id": 2,
    "name": "狗哥2号",
    "hm_girl_god_id": null
  },
  ......
]

Append多个

Go
func appendAssociation() {
	// 找出女神1号
	var g1 HMGirlGod
	DB.Model(&HMGirlGod{}).Find(&g1, 1)
	// 找出狗哥1号和狗哥2号
	var d1 HMDog
	var d2 HMDog
	DB.Model(&HMDog{}).Find(&d1, 1)
	DB.Model(&HMDog{}).Find(&d2, 2)
	ds := []HMDog{d1, d2}
	// 将狗哥1号、狗哥2号和女神1号关联
	DB.Model(&g1).Association("HMDogs").Append(&ds)
}

以上操作后HMDog表数据如下:

json
[
  {
    "id": 1,
    "name": "狗哥1号",
    "hm_girl_god_id": 1
  },
  {
    "id": 2,
    "name": "狗哥2号",
    "hm_girl_god_id": 1
  },
  ......
]

Append新的数据

TIP

  • 上面在使用Append时,操作数据库中的已有数据,也可以Append新创建的数据

  • 当使用Append创建新数据时,不用手动去Create数据,gorm会自动创建新的数据

Go
// 添加新成员
func appendNewRowAssociation() {
	// 找出女神1号
	var g1 HMGirlGod
	DB.Model(&HMGirlGod{}).Find(&g1, 1)
	d7 := HMDog{Name: "狗哥7号"}
	d8 := HMDog{Name: "狗哥8号"}
	// d7和d8是数据库中没有的数据,是新的数据,使用Append后,会自动在HMDog表中创建d7、d8,不用手动Create
	dSlice := []HMDog{d7, d8}
	DB.Model(&g1).Association("HMDogs").Append(&dSlice)
}

以上操作后HMDog表数据如下:

json
[
  {
    "id": 1,
    "name": "狗哥1号",
    "hm_girl_god_id": 1
  },
  {
    "id": 2,
    "name": "狗哥2号",
    "hm_girl_god_id": 1
  },
  {
    "id": 3,
    "name": "狗哥3号",
    "hm_girl_god_id": 2
  },
  {
    "id": 4,
    "name": "狗哥4号",
    "hm_girl_god_id": 2
  },
  {
    "id": 5,
    "name": "狗哥5号",
    "hm_girl_god_id": 3
  },
  {
    "id": 6,
    "name": "狗哥6号",
    "hm_girl_god_id": 3
  },
  {
    "id": 7,
    "name": "狗哥7号",
    "hm_girl_god_id": 1
  },
  {
    "id": 8,
    "name": "狗哥8号",
    "hm_girl_god_id": 1
  }
]

Replace

WARNING

  • 一对多的关系中,Replace(value)方法会将之前所有的关联都替换为value,所以使用的时候要注意下

  • 如下所示,目前女神1号关联的有狗哥1号狗哥2号狗哥7号狗哥8号,如果直接执行Replace(&d9),会清除之前女神1号关联的所有狗哥,然后将女神1号关联到狗哥7号

  • 所以使用Replace的时候要注意,是否要消除原先所有的关联

目前HMDog表数据如下:

json
[
  {
    "id": 1,
    "name": "狗哥1号",
    "hm_girl_god_id": 1
  },
  {
    "id": 2,
    "name": "狗哥2号",
    "hm_girl_god_id": 1
  },
  ......
  {
    "id": 7,
    "name": "狗哥7号",
    "hm_girl_god_id": 1
  },
  {
    "id": 8,
    "name": "狗哥8号",
    "hm_girl_god_id": 1
  }
]

新建一个狗哥9号,使用Replace将其和女神1号关联

Go
func replaceAssociation() {
	// 新创建狗哥9号
	d9 := HMDog{Name: "狗哥9号"}
	// 找出女神1号
	var g1 HMGirlGod
	DB.Model(&HMGirlGod{}).Find(&g1, 1)
	DB.Model(&g1).Association("HMDogs").Replace(&d9)
}

以上操作后HMDog表数据如下:

json
[
  {
    "id": 1,
    "name": "狗哥1号",
    "hm_girl_god_id": null
  },
  {
    "id": 2,
    "name": "狗哥2号",
    "hm_girl_god_id": null
  },
  ......
  {
    "id": 7,
    "name": "狗哥7号",
    "hm_girl_god_id": null
  },
  {
    "id": 8,
    "name": "狗哥8号",
    "hm_girl_god_id": null
  },
  {
    "id": 9,
    "name": "狗哥9号",
    "hm_girl_god_id": 1
  }
]

替换多个方式如下

Go
func replaceMulti() {
	// 找出狗哥3号
	var d3 HMDog
	DB.Model(&HMDog{}).Find(&d3, 3)
	// 找出狗哥4号
	var d4 HMDog
	DB.Model(&HMDog{}).Find(&d4, 4)
	// 找出狗哥9号
	var d9 HMDog
	DB.Model(&HMDog{}).Find(&d9, 9)
	// 将女神2号关联的狗哥替换为3号和9号
	// 找出女神2号
	var g2 HMGirlGod
	DB.Model(&HMGirlGod{}).Find(&g2, 2)
	g2Dogs := []HMDog{d3, d4}
	// 下面两行中的替换方式都可以
	//err := DB.Model(&g2).Association("HMDogs").Replace(&d3, &d4, &d9)
	err := DB.Model(&g2).Association("HMDogs").Replace(&g2Dogs)
	if err != nil {
		return
	}
}

Delete

Clear

和之前的用法一致

查询数据

查询女神2号关联的所有狗哥

Go
func queryData() {
	// 查询女神2号的所有狗哥
	var g2 HMGirlGod
	DB.Preload("HMDogs").Find(&g2, 2)
	// g2 is {2 女神2号 [{3 狗哥3号 2} {4 狗哥4号 2}]}
	fmt.Printf("g2 is %v\n", g2)
}

使用自定义预加载SQL方式查询

查询女神2号ID为3的狗哥

Go
func queryData() {
	// 查询女神2号下ID为3的狗哥
	var g2 HMGirlGod
	DB.Preload("HMDogs", func(db *gorm.DB) *gorm.DB {
		return db.Where("id = ?", 3)
	}).Find(&g2, 2)
	// g2 is {2 女神2号 [{3 狗哥3号 2}]}
	fmt.Printf("g2 is %v\n", g2)
}

使用预加载条件查询方式

查询女神2号ID为3的狗哥

Go
func queryByPreloadCondition() {
	// 查询女神2号下ID为3的狗哥
	var g2 HMGirlGod
	DB.Preload("HMDogs", "id = ?", 3).Find(&g2, 2)
	// g2 is {2 女神2号 [{3 狗哥3号 2}]}
	fmt.Printf("g2 is %v\n", g2)
}

链式预加载方式查询

  • 为狗哥增加一个钱包,每个人都有自己对应的小金库

修改结构体,为HMDog添加Wallet

Go
type Wallet struct {
	ID      uint
	Amount  float64
	HMDogID uint // 外键,就是HMDog.ID
}

type HMDog struct {
	ID          uint
	Name        string
	HMGirlGodID uint // 外键,就是HMGirlGod.ID
	Wallet      Wallet
}

type HMGirlGod struct {
	ID     uint
	Name   string
	HMDogs []HMDog
}

准备下数据,将1-3号8-9号狗哥和女神一号创建关联

json
[
  {
    "id": 1,
    "name": "狗哥1号",
    "hm_girl_god_id": 1
  },
  {
    "id": 2,
    "name": "狗哥2号",
    "hm_girl_god_id": 1
  },
  {
    "id": 3,
    "name": "狗哥3号",
    "hm_girl_god_id": 1
  },
  ......
  {
    "id": 7,
    "name": "狗哥7号",
    "hm_girl_god_id": 1
  },
  {
    "id": 8,
    "name": "狗哥8号",
    "hm_girl_god_id": 1
  },
  {
    "id": 9,
    "name": "狗哥9号",
    "hm_girl_god_id": 1
  }
]

钱包数据如下

狗哥ID狗哥编号金额
11200000
228000
33300000
47800
5860000
691000000

找出女神1号下面的所有关联的狗哥

Go
func linkQuery() {
	// 找出女神1号下面的所有关联的狗哥
	var g1 HMGirlGod
	DB.Preload("HMDogs").Find(&g1, 1)
	// g1 is {1 女神1号 [{1 狗哥1号 1 {0 0 0}} {2 狗哥2号 1 {0 0 0}} {3 狗哥3号 1 {0 0 0}} {7 狗哥7号 1 {0 0 0}} {8 狗哥8号 1 {0 0 0}} {9 狗哥9号 1 {0 0 0}}]}
	fmt.Printf("g1 is %v\n", g1)
}

上面的结果查出来了女神1号关联的所有狗哥,但是狗哥的钱包余额却没有查到,如果要查询余额,可以使用链式预加载查询,如下所示

Go
func linkQuery() {
	// 找出女神1号下面的所有关联的狗哥
	var g1 HMGirlGod
	//DB.Preload("HMDogs").Find(&g1, 1)
	// 使用链式查询
	DB.Preload("HMDogs.Wallet").Find(&g1, 1)
	// g1 is {1 女神1号 [{1 狗哥1号 1 {1 200000 1}} {2 狗哥2号 1 {2 8000 2}} {3 狗哥3号 1 {3 300000 3}} {7 狗哥7号 1 {4 800 7}} {8 狗哥8号 1 {5 60000 8}} {9 狗哥9号 1 {6 1e+06 9}}]}
	fmt.Printf("g1 is %v\n", g1)
}

链式预加载也可以多级预加载链式调用,比如找出女神1号下面的所有关联的ID > 3的狗哥

Go
func multiLinkQuery() {
	// 找出女神1号下面的所有关联的`ID > 3`的狗哥
	var g1 HMGirlGod
	//DB.Preload("HMDogs").Find(&g1, 1)
	// 使用多级预加载链式查询
	DB.Preload("HMDogs.Wallet").Preload("HMDogs", "id > ?", 3).Find(&g1, 1)
	fmt.Printf("g1 is %v\n", g1)
}

Joins查询

WARNING

  • Joins只支持一对一的场景,比如Belongs To或者Has One

如果我们只想找到女神1号关联的狗哥中余额大于10000的,余额小于10000的就不要出现,上面的方法就不满足了,如下可以看到,把余额不足10000的狗哥也带出来了

Go
func joinsQuery() {
	// 找出女神1号下面的所有关联的狗哥
	var g1 HMGirlGod
	// 查询女神1号关联的狗哥中  金额大于10000的
	DB.Preload("HMDogs.Wallet", "amount > ?", 10000).Find(&g1, 1)
	// g1 is {1 女神1号 [{1 狗哥1号 1 {1 200000 1}} {2 狗哥2号 1 {0 0 0}} {3 狗哥3号 1 {3 300000 3}} {7 狗哥7号 1 {0 0 0}} {8 狗哥8号 1 {5 60000 8}} {9 狗哥9号 1 {6 1e+06 9}}]}
	fmt.Printf("g1 is %v\n", g1)
}

使用Joins

Go
func joinsQuery() {
	// 找出女神1号下面的所有关联的狗哥
	var g1 HMGirlGod
	// 查询女神1号关联的狗哥中  金额大于10000的
	DB.Preload("HMDogs", func(db *gorm.DB) *gorm.DB {
		return db.Joins("Wallet").Where("amount > ?", 10000)
	}).Find(&g1, 1)
	// g1 is {1 女神1号 [{1 狗哥1号 1 {1 200000 1}} {3 狗哥3号 1 {3 300000 3}} {8 狗哥8号 1 {5 60000 8}} {9 狗哥9号 1 {6 1e+06 9}}]}
	fmt.Printf("g1 is %v\n", g1)
}

上述代码实际执行的sql如下

SQL
SELECT * FROM hm_dogs d LEFT JOIN wallets w ON d.id = w.hm_dog_id WHERE w.amount > '10000' AND d.hm_girl_god_id = '1';

查询女神1号关联的狗哥中金额大于10000并且ID大于3

Go
func joinsQuery() {
	var g1 HMGirlGod
	// 查询女神1号关联的狗哥中金额大于10000并且ID大于3的
	DB.Preload("HMDogs", func(db *gorm.DB) *gorm.DB {
		return db.Joins("Wallet").Where("amount > ? AND hm_dogs.id > ?", 10000, 3)
	}).Find(&g1, 1)
	fmt.Printf("g1 is %v\n", g1)
}