建立数据库连接
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)
}
由于多态的结构体的数据不可以同时被多表拥有,所以多态关系只存在于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")
可以使用标签 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")
实验案例如下
当想记录男孩女孩玩具之间的关系时,关联关系是,男孩和女孩是两个不同的模型,他们都与玩具存在一对一关系。则常规建表方法为:
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
}
当使用多态,则可简化为:
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
}
现在boy与girl各创建一条记录,观察多态的变化。
创建记录代码:
func InsertPoly() {
OpenDB()
boy := &Boys{BoyName: "小胖", Toy: Toys{
ToyName: "挖掘机",
}}
girl := &Girls{GirlName: "小美", Toy: Toys{ToyName: "小风车"}}
db.Create(boy)
db.Create(girl)
}
boy与girl表没有特殊变化,正常记录,不做展示。
toy表:
此时已经创建联系,owner_type记录的表名字,owner_id记录的玩具拥有者。
尝试查询:
func QueryPoly() {
OpenDB()
boy := &Boys{}
db.Preload("Toy").Find(boy, 1)
fmt.Println(boy)
}
查询成功。
尝试修改关联:
在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)
}
标签 | 描述 |
---|---|
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
}
接下来重写外键,让外键从CardUserID改为NewWaijian:
type CardUser struct {
gorm.Model
name string
CreditCard CreditCard `gorm:"foreignKey:NewWaijian""`
}
type CreditCard struct {
gorm.Model
Number string
NewWaijian uint
}
func InserteHasone() {
OpenDB()
user := &CardUser{CreditCard: CreditCard{Number: "123"}}
db.Create(user)
}
发现引用值依然是主表的主键。
现在想把引用改为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
}
实验时出现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)
}
在多对多关系中,由于连接关系储存在连接表中,所以外键和引用值的参数,与其他关系不同。
首先建立表
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;"`
}
这里故意不区分两张表内的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)
}
操作成功,外键为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;"`
}
现在将关联表名字反转为classes——students
发现创建的表依旧存在问题。
最后实验多次,发现是创建函数出现问题。
func CreateMany2Many() {
OpenDB()
db.AutoMigrate(&Classes{})
InsertMany2Many()
}
改为
func CreateMany2Many() {
OpenDB()
db.AutoMigrate(&Students{})
InsertMany2Many()
}
后实验成功。
对于本次实验目的得出结论一,外键一般只能重写为标签所在的结构体里的字段,引用指向关系表内的字段,当外键字段与引用字段同名时,也遵循这个规则,外键指向标签所在结构体内的name,引用指向关系表内的name。
通常认为,存在外键的表为副表,除开belongsto关系时,通常建立主表的时候,会自动创建副表,此实验也相同,因为一对一与一对多关系中主表通常嵌套副表结构体,所以会自动创建。而本次实验优先创立副表,虽然包括连接表在内的三张表都成功创建,但是由于优先创立副表的关系,重写操作均失效,可能原因是,由于many2many关系中,两个模型相互嵌套,无论优先创建那个表,都会自动创建另一张表,但副表建立时,主表还未建立,副表内的重写应用标签无法对应上主表内的字段而无法运行,所以略过了标签,导致未执行重写操作,解决办法,养成优先创立主表的习惯。