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)
}
belongs to
建立belongs to关系
belongs to 会与另一个模型建立了一对一的连接。 == 这种模型的每一个实例都“属于”另一个模型的一个实例==
例如,您的应用包含 user 和 company,并且每个 user 能且只能被分配给一个 company。下面的类型就表示这.z种关系。 注意,在 User 对象中,有一个和 Company 一样的 CompanyID。 默认情况下, CompanyID 被隐含地用来在 User 和 Company 之间创建一个外键关系, 因此必须包含在 User 结构体中才能填充 Company 内部结构体。
// `User` 属于 `Company`,`CompanyID` 是外键
type User struct {
gorm.Model
Name string
CompanyID int
Company Company
}
type Company struct {
ID int
Name string
}
func CreateBelongsTo() {
user := &User{}
company := &Company{}
db.AutoMigrate(user, company)
}
Users表
Companies表
重写外键
要定义一个 belongs to 关系,数据库的表中必须存在外键。默认情况下,外键的名字,使用拥有者的类型名称加上表的主键的字段名字
例如,定义一个User实体属于Company实体,那么外键的名字一般使用CompanyID。
GORM同时提供自定义外键名字的方式,如下例所示。
type User struct {
gorm.Model
Name string
CompanyRefer int
Company Company `gorm:"foreignKey:CompanyRefer"`
// 使用 CompanyRefer 作为外键
}
type Company struct {
ID int
Name string
}
has one
建立has one关系
has one 与另一个模型建立一对一的关联,但它和一对一关系有些许不同。 这种关联表明一个模型的每个实例都包含或拥有另一个模型的一个实例。
例如,您的应用包含 user 和 credit card 模型,且每个 user 只能有一张 credit card。
// User 有一张 CreditCard,UserID 是外键
type User struct {
gorm.Model
CreditCard CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
实验案例如下
func CreateHasOne() {
OpenDB()
user := &CardUser{}
card := &CreditCard{}
db.AutoMigrate(user, card)
}
重写外键
对于 has one 关系,同样必须存在外键字段。拥有者将把属于它的模型的主键保存到这个字段。
这个字段的名称通常由 has one 模型的类型加上其 主键 生成,对于上面的例子,它是 UserID。
为 user 添加 credit card 时,它会将 user 的 ID 保存到自己的 UserID 字段。
如果你想要使用另一个字段来保存该关系,你同样可以使用标签 foreignKey 来更改它,例如:
type User struct {
gorm.Model
CreditCard CreditCard `gorm:"foreignKey:UserName"`
// use UserName as foreign key
}
type CreditCard struct {
gorm.Model
Number string
UserName string
}
has one与belongs to之间的不同点,与相同操作
以下属于个人实验分析得到的结论,如果不对还请担待
创建表上分析
暂时发现,在结构体层面,创建belongsto关系外键与嵌套的结构体都存在于副表中。创建has to关系时,外键在副表中,嵌套的结构体在主表中。
在自动创建方面上,处于belongsto关系中,创建副表时会自动创建主表,创建主表时不会自动创建副表。
而hasone关系中,不存在自动创建关联表的关系。
创建记录
创建记录时,允许独自创立主表,不能独自创建副表不添加外键,或者不嵌套主表结构体。
报错代码
type User struct {
gorm.Model
UserName string
CompanyID string
Company Company
}
type Company struct {
ID int
CompanyName string
}
func InsertBelongsTo() {
OpenDB()
//company := &Company{CompanyName: "hbzyydx"}
user := &User{UserName: "ylj"}
db.Create(user)
}
在存在嵌套关系时,创建记录,会自动创建外键,并产生关联
func InserteHasone() {
card := &CreditCard{Model: gorm.Model{ID: 1}}
user := &CardUser{CreditCard: *card}
OpenDB()
db.Create(user)
}
查询功能(预加载)
基本与单表查询没差异,但是多表查询中,有个很关键的方法 预加载 ,在没有调用预加载功能时,gorm不会自动查询出关联的信息,例如:
type User struct {
gorm.Model
UserName string
CompanyID string
Company Company
}
type Company struct {
ID int
CompanyName string
}
func QueryBelongsto() {
OpenDB()
user := &User{}
db.Model(user).Where("id=?", 1).Find(user)
fmt.Println(user)
}
user关联的Company 信息并未显示。
现在调用预加载方法Preload
func QueryBelongsto() {
OpenDB()
user := &User{}
db.Model(user).Where("id=?", 1).Preload("Company").Find(user)
fmt.Println(user)
}
发现Preload方法中参数的名称应该与结构体里的字段名相同,而非与关联的表面相同,实验中Company的表名在mysql中为company,preload方法的参数用小写的company时查询不出结果,改为与字段名相同的大写Company则可以允许运行。
预加载功能实验
所以利用预加载功能可以查询出被关联的表的信息,在此之下,引出一下几个问题并经行实验。
- 在链式关联的情况下,预加载方法需要逐层预加载,还是只需要预加载所需表。
- 预加载功能是否可以使用约束。
- 强行在belongsto与hasone上建立多对一关系,会发生什么情况
1,在链式关联的情况下,预加载方法需要逐层预加载,还是只需要预加载所需表。
链式关系例如在上文创建的belongsto上添加一层用户信息表,如
Info-User-Company:
type Info struct {
gorm.Model
Age int
Sex string
UserID int
user User
}
type User struct {
gorm.Model
UserName string
CompanyID string
Company Company
}
type Company struct {
ID int
CompanyName string
}
添加记录部分跳过
此时想通过Info表查询到Company表,正常逻辑为逐层预加载,即:
func QueryBelongsto() {
OpenDB()
info := &Info{}
db.Model(info).Where("id=?", 1).Preload("Company").Preload("User").Find(info)
fmt.Println(info)
}
发现当与Model内指定的表发生越级情况时,需要在预加载语句中添加上一级的表名。
修改后
func QueryBelongsto() {
OpenDB()
info := &Info{}
db.Model(info).Where("id=?", 1).Preload("User.Company").Preload("User").Find(info)
fmt.Println(info)
}
查询成功。
现只预加载所需的Company表
func QueryBelongsto() {
OpenDB()
info := &Info{}
db.Model(info).Where("id=?", 1).Preload("User.Company").Find(info)
fmt.Println(info)
}
也成功查询到结果,
现增加到四层链式关系
type Test struct {
gorm.Model
InfoID int
Info Info
}
实验未成功,问题待解决
当链式结构增加都四层的时候,发现无论经行那儿三层的链式查询都有结果返回,但查询四层的时候,结果报错。暂时未能解决问题。
错误代码:
func QueryBelongsto() {
OpenDB()
test := &Test{}
db.Model(test).Where("id=?", 1).Preload("User.Company").Preload("Info.User").Preload("Info").Find(test)
fmt.Println(test)
}
报错
暂时只能利用笨办法,分段经行查询,
func QueryBelongsto() {
OpenDB()
test := &Test{}
db.Preload("Info.User").Preload("Info").First(test, 1)
id := test.Info.UserID
user := &User{}
println(id)
db.Preload("Company").Find(user, id)
fmt.Println(user)
}
- 预加载功能是否可以使用约束
实验时查询官方文档发现官方提供了预加载时添加约束条件功能。
官方提供了两种方法
// 带条件的预加载 Order
db.Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4) AND state NOT IN ('cancelled');
db.Where("state = ?", "active").Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
// SELECT * FROM users WHERE state = 'active';
// SELECT * FROM orders WHERE user_id IN (1,2) AND state NOT IN ('cancelled');
2,自定义预加载 SQL
您可以通过 func(db *gorm.DB) *gorm.DB 实现自定义预加载 SQL,例如:
db.Preload("Orders", func(db *gorm.DB) *gorm.DB {
return db.Order("orders.amount DESC")
}).Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4) order by orders.amount DESC;
因为一对一关系中,条件限制有限,具体实验在后续多对一和多对多中经行实验,方便观察结果。
3.强行在belongsto与hasone上建立多对一关系,会发生什么情况
belongsto
记录结构:
两个test对一个info
分别对两个test记录经行查询。
结果:
发现两个结果都成功查询,并且未影响预加载功能。
结论:肯尼因为嵌套关系存在于附表中,所以强行将belongsto设置为多对一关系,在查询功能上未发现异常。
hasone:
记录结构:
三个CreditCard对一个User
func QueryHasOne() {
OpenDB()
user := &CardUser{}
db.Preload("CreditCard").First(user, 4)
fmt.Println(user)
}
结果
发现User下的信用卡只显示一张,且为最后一张。
分析:
可能因为嵌套的结构体在主表中,在插入时覆盖了结构体中的信息,所以只显示一条记录,且为最后修改的一条记录。