gorm基础-5.一对多关系

我们先从一对多开始多表关系的学习

因为一对多的关系生活中到处都是

例如

老板与员工
女神和舔狗
老师和学生
班级与学生
用户与文章
...

一对多关系 表结构建立

在gorm中,官方文档是把一对多关系分为了两类,

Belongs To 属于谁

Has Many 我拥有的

他们本来是一起的,本教程把它们合在一起讲

我们以用户和文章为例

一个用户可以发布多篇文章,一篇文章属于一个用户

type User struct {
  ID       uint      `gorm:"size:4"`
  Name     string    `gorm:"size:8"`
  Articles []Article // 用户拥有的文章列表
}

type Article struct {
  ID     uint   `gorm:"size:4"`
  Title  string `gorm:"size:16"`
  UserID uint   // 属于   这里的类型要和引用的外键类型一致,包括大小
  User   User   // 属于
}

关于外键命名,外键名称就是关联表名+ID,类型是uint

重写外键关联

type User struct {
  ID       uint      `gorm:"size:4"`
  Name     string    `gorm:"size:8"`
  Articles []Article `gorm:"foreignKey:UID"` // 用户拥有的文章列表
}

type Article struct {
  ID    uint   `gorm:"size:4"`
  Title string `gorm:"size:16"`
  UID   uint   // 属于
  User  User   `gorm:"foreignKey:UID"` // 属于
}

这里有个地方要注意

我改了Article 的外键,将UID作为了外键,那么User这个外键关系就要指向UID

与此同时,User所拥有的Articles也得更改外键,改为UID

重写外键引用

type User struct {
  ID       uint      `gorm:"size:4"`
  Name     string    `gorm:"size:8"`
  Articles []Article `gorm:"foreignKey:UserName;references:Name"` // 用户拥有的文章列表
}

type Article struct {
  ID       uint   `gorm:"size:4"`
  Title    string `gorm:"size:16"`
  UserName string
  User     User `gorm:"references:Name"` // 属于
}

这一块的逻辑比较复杂

比如有1个用户

idname
1枫枫

之前的外键关系是这样表示文章的

idtitleuser_id
1python1
2javascript1
3golang1

如果改成直接关联Name,那就变成了这样

idtitleuser_name
1python枫枫
2javascript枫枫
3golang枫枫

虽然这样很方便,但是非常不适合在实际项目中这样用

我们还是用第一版的表结构做一对多关系的增删改查

一对多的添加

创建用户,并且创建文章

a1 := Article{Title: "python"}
a2 := Article{Title: "golang"}
user := User{Name: "枫枫", Articles: []Article{a1, a2}}
DB.Create(&user)

gorm自动创建了两篇文章,以及创建了一个用户,还将他们的关系给关联上了

创建文章,关联已有用户

a1 := Article{Title: "golang零基础入门", UserID: 1}
DB.Create(&a1)
var user User
DB.Take(&user, 1)
DB.Create(&Article{Title: "python零基础入门", User: user})

外键添加

给现有用户绑定文章

var user User
DB.Take(&user, 2)

var article Article
DB.Take(&article, 5)

user.Articles = []Article{article}
DB.Save(&user)

也可以用Append方法

var user User
DB.Take(&user, 2)

var article Article
DB.Take(&article, 5)

//user.Articles = []Article{article}
//DB.Save(&user)

DB.Model(&user).Association("Articles").Append(&article)

给现有文章关联用户

var article Article
DB.Take(&article, 5)

article.UserID = 2
DB.Save(&article)

也可用Append方法

var user User
DB.Take(&user, 2)

var article Article
DB.Take(&article, 5)

DB.Model(&article).Association("User").Append(&user)

查询

查询用户,显示用户的文章列表

var user User
DB.Take(&user, 1)
fmt.Println(user)

直接这样,是显示不出文章列表

预加载

我们必须要使用预加载来加载文章列表

var user User
DB.Preload("Articles").Take(&user, 1)
fmt.Println(user)

预加载的名字就是外键关联的属性名

查询文章,显示文章用户的信息

同样的,使用预加载

var article Article
DB.Preload("User").Take(&article, 1)
fmt.Println(article)

嵌套预加载

查询文章,显示用户,并且显示用户关联的所有文章,这就得用到嵌套预加载了

var article Article
DB.Preload("User.Articles").Take(&article, 1)
fmt.Println(article)

带条件的预加载

查询用户下的所有文章列表,过滤某些文章

var user User
DB.Preload("Articles", "id = ?", 1).Take(&user, 1)
fmt.Println(user)

这样,就只有id为1的文章被预加载出来了

自定义预加载

var user User
DB.Preload("Articles", func(db *gorm.DB) *gorm.DB {
  return db.Where("id in ?", []int{1, 2})
}).Take(&user, 1)
fmt.Println(user)

删除

级联删除

删除用户,与用户关联的文章也会删除

var user User
DB.Take(&user, 1)
DB.Select("Articles").Delete(&user)

清除外键关系

删除用户,与将与用户关联的文章,外键设置为null

var user User
DB.Preload("Articles").Take(&user, 2)
DB.Model(&user).Association("Articles").Delete(&user.Articles)

<think>嗯,用户想了解GORM一对一关系的正向和反向操作。首先,我得回忆一下GORM处理一对一关系的方式。GORM使用结构体标签来定义模型之间的关系,一对一关系通常通过`has one`和`belongs to`来实现。 首先,正向操作应该是指从主模型访问关联模型,比如用户有一个资料表,通过User访问Profile。这时候需要在User结构体中定义Profile字段,并使用`gorm:"foreignKey:UserID"`标签,外键在Profile表中。这样,当查询User时,GORM会自动加载关联的Profile数据,可能需要使用Preload方法。 然后是反向操作,比如从Profile找到对应的User。这时候Profile结构体应该有一个User字段,并标记为`belongs to`,外键同样是UserID。这样,查询Profile时可以通过Preload加载关联的User。 接下来要考虑的是如何添加记录。正向操作中,创建User时可能需要同时创建Profile,使用事务来保证一致性。而反向操作则是先创建User,再创建Profile,并设置UserID外键。 查询的时候,两种方向都需要Preload来预加载关联数据,避免N+1查询问题。删除的话,可能需要设置级联删除,或者手动处理关联记录,这取决于外键约束的设置。 还需要注意结构体标签的正确性,比如foreignKey和references的配置,确保GORM能正确识别关系。另外,引用中的事务处理(Begin、Commit、Rollback)在添加记录时很重要,特别是当需要同时操作个表的时候。 用户提供的引用里提到了hooks和关联查询,可能需要在模型中定义AfterFind之类的钩子来自动处理数据加载,但通常Preload已经足够。另外,参考引用中的事务管理,确保数据的一致性。 可能用户在实际应用中会遇到外键不匹配或者预加载失败的问题,需要检查结构体定义和标签是否正确。还有,如何处理零值或空值的情况,比如Profile可能为空,这时候查询结果是否正确。 总结一下,回答应该分正向和反向操作,涵盖定义模型、添加记录、查询和删除的步骤,并给出示例代码。同时,提醒外键约束和事务的使用,以及可能遇到的问题。</think># GORM一对一关系正向与反向操作详解 ## 一、模型定义 ### 1.1 正向操作模型(User → Profile) ```go type User struct { ID uint `gorm:"primaryKey"` Name string Profile Profile `gorm:"foreignKey:UserID"` // 外键在Profile表 } type Profile struct { ID uint UserID uint // 外键字段 Email string } ``` ### 1.2 反向操作模型(Profile → User) ```go type Profile struct { ID uint UserID uint User User `gorm:"foreignKey:UserID"` // 外键关联 Email string } ``` ## 二、基础操作 ### 2.1 添加记录 **正向操作示例**: ```go db.Transaction(func(tx *gorm.DB) error { user := User{Name: "Alice", Profile: Profile{Email: "alice@example.com"}} if err := tx.Create(&user).Error; err != nil { tx.Rollback() return err } return tx.Commit().Error }) ``` **反向操作示例**: ```go user := User{Name: "Bob"} db.Create(&user) profile := Profile{ UserID: user.ID, Email: "bob@example.com" } db.Create(&profile) ``` ### 2.2 查询操作 **正向查询**: ```go var user User db.Preload("Profile").First(&user, 1) // 访问 user.Profile.Email ``` **反向查询**: ```go var profile Profile db.Preload("User").First(&profile, 1) // 访问 profile.User.Name ``` ### 2.3 删除操作 ```go // 级联删除(需要设置数据库外键约束) db.Select("Profile").Delete(&User{ID: 1}) // 独立删除 db.Delete(&Profile{}, 1) ``` ## 三、高级特性 1. **预加载优化**: ```go db.Preload(clause.Associations).Find(&users) // 加载所有关联 ``` 2. **自定义外键**: ```go type User struct { UUID string `gorm:"primaryKey"` Profile Profile `gorm:"foreignKey:UserUUID;references:UUID"` } type Profile struct { UserUUID string } ``` 3. **自动创建/更新**: ```go db.Session(&gorm.Session{FullSaveAssociations: true}).Save(&user) ``` ## 四、常见问题解决方案 1. **N+1查询问题**: - 始终使用`Preload`进行关联预加载 - 批量查询时使用`PreloadAll`方法 2. **零值处理**: ```go db.Joins("Profile").First(&user, 1) // 使用JOIN查询 ``` 3. **事务管理**: ```go tx := db.Begin() if err := tx.Model(&user).Association("Profile").Replace(&newProfile); err != nil { tx.Rollback() } tx.Commit() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值