golang学习笔记(13)-gorm多态和引用关联标签

    本文详细介绍了GORM库在Go语言中的使用,包括数据库连接、多态关联、引用和关联标签的设置。通过实例展示了如何在hasOne和hasMany关系中实现多态,以及如何处理多对多关系。在多对多关系中,外键和引用的重写规则进行了探讨,并解决了因创建顺序导致的问题。实验揭示了在外键字段与引用字段同名时的处理方式和外键重写的限制。

    摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    gorm多态,引用和关联标签

    准备工作

    建立数据库连接

    import (
    	"fmt"
    	_ "github.com/go-sql-driver/mysql"
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"log"
    )
    
    var db *gorm.DB
    
    func OpenDB() {
    	dsn := "root:adss123@tcp(127.0.0.1:3306)/go_db?charset=utf8mb4&parseTime=True&loc=Local"
    	res, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    	db = res
    	if err != nil {
    		log.Fatal(err)
    	}
    	fmt.Printf("成功:%v\n", db)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    多态

    由于多态的结构体的数据不可以同时被多表拥有,所以多态关系只存在于hasOne和hasMany中。
    在上述两种关系的应用中,时常出现多表添加关系,每次添加一张表,都需要重新修改表的结构,增加外键,导致维护表的工作量增加,此时需要多态的概念来简化工作。
    GORM 为 has one 和 has many 提供了多态关联支持,它会将拥有者实体的表名、主键都保存到多态类型的字段中。

    type Dog struct {
      ID   int
      Name string
      Toys []Toy `gorm:"polymorphic:Owner;"`
    }
    
    type Toy struct {
      ID        int
      Name      string
      OwnerID   int
      OwnerType string
    }
    
    db.Create(&Dog{Name: "dog1", Toys: []Toy{{Name: "toy1"}, {Name: "toy2"}}})
    // INSERT INTO `dogs` (`name`) VALUES ("dog1")
    // INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1","1","dogs"), ("toy2","1","dogs")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    可以使用标签 polymorphicValue 来更改多态类型的值,例如:

    type Dog struct {
      ID   int
      Name string
      Toys []Toy `gorm:"polymorphic:Owner;polymorphicValue:master"`
    }
    
    type Toy struct {
      ID        int
      Name      string
      OwnerID   int
      OwnerType string
    }
    
    db.Create(&Dog{Name: "dog1", Toy: []Toy{{Name: "toy1"}, {Name: "toy2"}}})
    // INSERT INTO `dogs` (`name`) VALUES ("dog1")
    // INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1","1","master"), ("toy2","1","master")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    实验案例如下
    当想记录男孩女孩玩具之间的关系时,关联关系是,男孩和女孩是两个不同的模型,他们都与玩具存在一对一关系。则常规建表方法为:

    type Boys struct {
    	gorm.Model
    	BoyName string
    	Toy     Toys
    }
    type Girls struct {
    	gorm.Model
    	GirlName string
    	Toy      Toys
    }
    type Toys struct {
    	gorm.Model
    	ToyName string
    	BoysID  uint
    	GirlsID uint
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    当使用多态,则可简化为:

    type Boys struct {
    	gorm.Model
    	BoyName string
    	Toy     Toys `gorm:"polymorphic:Owner;"`
    }
    type Girls struct {
    	gorm.Model
    	GirlName string
    	Toy      Toys `gorm:"polymorphic:Owner;"`
    }
    type Toys struct {
    	gorm.Model
    	ToyName   string
    	OwnerID   uint
    	OwnerType string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    表结构为:
    toys表
    在这里插入图片描述
    boys表
    在这里插入图片描述
    girls表
    在这里插入图片描述

    现在boy与girl各创建一条记录,观察多态的变化。
    创建记录代码:

    func InsertPoly() {
    	OpenDB()
    	boy := &Boys{BoyName: "小胖", Toy: Toys{
    		ToyName: "挖掘机",
    	}}
    	girl := &Girls{GirlName: "小美", Toy: Toys{ToyName: "小风车"}}
    	db.Create(boy)
    	db.Create(girl)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    boy与girl表没有特殊变化,正常记录,不做展示。
    toy表:
    在这里插入图片描述
    此时已经创建联系,owner_type记录的表名字,owner_id记录的玩具拥有者。
    尝试查询:

    func QueryPoly() {
    	OpenDB()
    	boy := &Boys{}
    	db.Preload("Toy").Find(boy, 1)
    	fmt.Println(boy)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    查询成功。
    尝试修改关联:
    在boys表中多建立一个记录
    在这里插入图片描述
    讲小胖和玩具的关联,替换到小恶霸上。

    func QueryPoly() {
    	OpenDB()
    	boy := &Boys{Model: gorm.Model{
    		ID: 2,
    	}}
    	db.Model(boy).Association("Toy").Replace(&Toys{Model: gorm.Model{ID: 1}})
    	fmt.Println(boy)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    结果
    在这里插入图片描述
    替换成功。

    引用和关联标签

    关联标签

    标签描述
    foreignKey指定当前模型的列作为连接表的外键
    references指定引用表的列名,其将被映射为连接表外键
    polymorphic指定多态类型,比如模型名
    polymorphicValue指定多态值、默认表名
    many2many指定连接表表名
    joinForeignKey指定连接表的外键列名,其将被映射到当前表
    joinReferences指定连接表的外键列名,其将被映射到引用表
    constraint关系约束,例如:OnUpdate、OnDelete

    多态在上文已经学习,现着重学习外键与外键引用

    引用

    除多对多

    在has one的关联关系下,常规建表副表会定义一个与主表名称相同的XXXID来作为外键,所引用的值是主表的主键。例如:

    type CardUser struct {
    	gorm.Model
    	CreditCard CreditCard
    }
    
    type CreditCard struct {
    	gorm.Model
    	Number     string
    	CardUserID uint
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    接下来重写外键,让外键从CardUserID改为NewWaijian:

    type CardUser struct {
       gorm.Model
       name string
       CreditCard CreditCard `gorm:"foreignKey:NewWaijian""`
    }
    
    type CreditCard struct {
       gorm.Model
       Number     string
       NewWaijian uint
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    现在增加一条记录

    func InserteHasone() {
    	OpenDB()
    	user := &CardUser{CreditCard: CreditCard{Number: "123"}}
    	db.Create(user)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    发现引用值依然是主表的主键。
    现在想把引用改为User的名称。

    type CardUser struct {
    	gorm.Model
    	Name       string     `gorm:"type:varchar(100);index"`
    	CreditCard CreditCard `gorm:"foreignKey:NewWaijian;references:Name;"`
    }
    
    type CreditCard struct {
    	gorm.Model
    	Number     string
    	NewWaijian string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    实验时出现Error 1170: BLOB/TEXT column ‘new_waijian’ used in key specification without a key length错误,错误发生的原因是因为MySQL只能将BLOB/TEXT类型字段设置索引为BLOB/TEXT数据的钱N个字符,因此错误常常发生在字段被定义为TEXT/BLOB类型或者和TEXT/BLOB同质的数据类型,如TINYTEXT,MEDIUMTEXT,LONGTEXT。

    解决方案是将unique限制和索引从TEXT/BLOB字段中移除,或者是设置另一个字段为主键,如果你不愿意这样做并且想在TEXT/BLOB上加限制,那么你可以尝试将这个字段更改为VARCHAR类型,同时给他一个限制长度,默认VARCHAR最多可以限定在255个字符,并且限制要在声明类型的右边指明,如VARCHAR(200)将会限制仅仅200个字符


    创建一条记录

    func InserteHasone() {
    	OpenDB()
    	user := &CardUser{Name: "ylj", CreditCard: CreditCard{Number: "123"}}
    	db.Create(user)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    这时外键引用的则是主键里的名称值。

    多对多

    在多对多关系中,由于连接关系储存在连接表中,所以外键和引用值的参数,与其他关系不同。
    首先建立表

    type Students struct {
    	gorm.Model
    	Name    string    `gorm:"type:varchar(100);index"`
    	Classes []Classes `gorm:"many2many:students_classes;foreignKey:Name;references:Name;"`
    }
    
    type Classes struct {
    	gorm.Model
    	Name     string     `gorm:"type:varchar(100);index"`
    	Students []Students `gorm:"many2many:students_classes;"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这里故意不区分两张表内的Name,观察重写操作。
    创建一条记录,并关联。

    func InsertMany2Many() {
    	OpenDB()
    	class := &Classes{Name: "math"}
    	stu := &Students{Name: "stu1"}
    	db.Create(class)
    	db.Create(stu)
    	db.Model(stu).Association("Classes").Append(class)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    操作成功,外键为students_name,引用值为classes_name

    再将重写标签,写在classes结构体内

    type Students struct {
    	gorm.Model
    	Name    string    `gorm:"type:varchar(100);index"`
    	Classes []Classes `gorm:"many2many:students_classes;"`
    }
    
    type Classes struct {
    	gorm.Model
    	Name     string     `gorm:"type:varchar(100);index"`
    	Students []Students `gorm:"many2many:students_classes;foreignKey:Name;references:Name;"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述
    发现重写操作失败


    现在将关联表名字反转为classes——students
    发现创建的表依旧存在问题。
    在这里插入图片描述


    最后实验多次,发现是创建函数出现问题。

    func CreateMany2Many() {
    	OpenDB()
    	db.AutoMigrate(&Classes{})
    	InsertMany2Many()
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    改为

    func CreateMany2Many() {
    	OpenDB()
    	db.AutoMigrate(&Students{})
    	InsertMany2Many()
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    后实验成功。
    在这里插入图片描述
    对于本次实验目的得出结论一,外键一般只能重写为标签所在的结构体里的字段,引用指向关系表内的字段,当外键字段与引用字段同名时,也遵循这个规则,外键指向标签所在结构体内的name,引用指向关系表内的name。

    意外发现

    通常认为,存在外键的表为副表,除开belongsto关系时,通常建立主表的时候,会自动创建副表,此实验也相同,因为一对一与一对多关系中主表通常嵌套副表结构体,所以会自动创建。而本次实验优先创立副表,虽然包括连接表在内的三张表都成功创建,但是由于优先创立副表的关系,重写操作均失效,可能原因是,由于many2many关系中,两个模型相互嵌套,无论优先创建那个表,都会自动创建另一张表,但副表建立时,主表还未建立,副表内的重写应用标签无法对应上主表内的字段而无法运行,所以略过了标签,导致未执行重写操作,解决办法,养成优先创立主表的习惯。

    登录后您可以享受以下权益:

    ×
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值

    举报

    选择你想要举报的内容(必选)
    • 内容涉黄
    • 政治相关
    • 内容抄袭
    • 涉嫌广告
    • 内容侵权
    • 侮辱谩骂
    • 样式问题
    • 其他
    点击体验
    DeepSeekR1满血版
    程序员都在用的中文IT技术交流社区

    程序员都在用的中文IT技术交流社区

    专业的中文 IT 技术社区,与千万技术人共成长

    专业的中文 IT 技术社区,与千万技术人共成长

    关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

    关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

    客服 返回顶部