關係
概述
資料庫表之間相互聯繫是很常見的。 例如,一篇博客文章可能有很多評論,或者一個訂單可能與下訂單的用戶相關聯。 Orm 簡化了管理和處理這些關係的方式,並且可以處理各種常見的關係:
定義關係
一對一
一對一關係是非常基本的資料庫關係類型。 例如,User模型可能與一個Phone模型相關聯。
type User struct {
orm.Model
Name string
Phone *Phone
}
type Phone struct {
orm.Model
UserID uint
Name string
}使用 Orm 時,它會根據父模型的名稱自動分配外鍵到該關係。 例如,Phone 模型默認被假設擁有 UserID 外鍵。 然而,如果你希望更改這種約定,可以在 User 模型中的 Phone 字段添加 foreignKey 標籤。 (這也適用於其他關係。)
type User struct {
orm.Model
Name string
Phone *Phone `gorm:"foreignKey:UserName"`
}
type Phone struct {
orm.Model
UserName string
Name string
}此外,使用 Orm 時假設外鍵應與父模型的主鍵欄位相匹配。 這意味著 Orm 將在 Phone 記錄的 UserId 欄位中搜索用戶的 ID 欄位值。 如果你希望使用其他於 ID 的主鍵值,可以在 User 模型中的 Phone 字段添加 "Tag" 引用。 要做到這一點,只需將第三個參數傳遞到 hasOne 方法中。 (其他關係的設置也類似。)
type User struct {
orm.Model
Name string
Phone *Phone `gorm:"foreignKey:UserName;references:name"`
}
type Phone struct {
orm.Model
UserName string
Name string
}定義關係的反向
我們可以從 User 模型訪問 Phone 模型。 現在,我們需要在 Phone 模型上建立一個關係,這將使我們能夠訪問電話的擁有者。 為此,我們可以在 Phone 模型中定義一個 User 字段。
type User struct {
orm.Model
Name string
}
type Phone struct {
orm.Model
UserID uint
Name string
User *User
}一對多
一對多關係用以定義單一模型為一個或多個子模型的父模型的關係。 例如,一篇博客文章可能有無限數量的評論。 像所有其他 Orm 關係一樣,一對多的關係通過在 Orm 模型中定義一個欄位來定義:
type Post struct {
orm.Model
Name string
Comments []*Comment
}
type Comment struct {
orm.Model
PostID uint
Name string
}請記住,Orm 將自動確定 Comment 模型的適當外鍵欄位。 根據慣例,Orm 將使用父模型的 "駝峰命名" 方式來命名外鍵欄位,並以 ID 作為後綴。 因此,在此範例中,Orm 將假設 Comment 模型的外鍵欄位為 PostID。
一對多(反向)/ 屬於
現在我們可以訪問所有帖子的評論,讓我們定義一個關係,使評論能夠訪問其父帖。 要定義 一對多 關係的反向,請在子模型上定義一個關係方法,並調用 belongsTo 方法:
type Post struct {
orm.Model
Name string
Comments []*Comment
}
type Comment struct {
orm.Model
PostID uint
Name string
Post *Post
}多對多關係
多對多的關係比 一對一 和 一對多 的關係稍微複雜。 一個多對多關係的例子是用戶擁有多個角色,並且這些角色也由該應用程式中的其他用戶共享。 例如,一個用戶可能會被分配 "作者" 和 "編輯" 的角色;不過,這些角色也可以分配給其他用戶。 因此,一個用戶擁有多個角色,而一個角色擁有多個用戶。
表結構
要定義這種關係,需要三個資料庫表:users,roles 與 role_user。 role_user 表名稱可以自定義,並且它包含 user_id 和 role_id 欄位。 該表用作連結用戶和角色的中間表。
請記住,因為角色可以屬於多個用戶,我們不能簡單地在 roles 表中置入 user_id 欄位。 這將意味著角色只能屬於單一用戶。 為了支持角色分配給多個用戶,我們需要 role_user 表。 我們可以這樣總結關係的表結構:
users
id - 整數
name - 字串
roles
id - 整數
name - 字串
role_user
user_id - 整數
role_id - 整數模型結構
我們可以在 User 模型上定義一個 Roles 字段:
type User struct {
orm.Model
Name string
Roles []*Role `gorm:"many2many:role_user"`
}
type Role struct {
orm.Model
Name string
}定義關係的反向
要定義關係的反向,只需在 Role 模型中定義 Users 字段並附加標籤。
type User struct {
orm.Model
Name string
Roles []*Role `gorm:"many2many:role_user"`
}
type Role struct {
orm.Model
Name string
Users []*User `gorm:"many2many:role_user"`
}自定義中間表
一般來說,中間表外鍵的命名是基於父模型名稱的「蛇形」命名法,你可以通過 joinForeignKey 和 joinReferences 來覆蓋它們:
type User struct {
orm.Model
Name string
Roles []*Role `gorm:"many2many:role_user;joinForeignKey:UserName;joinReferences:RoleName"`
}
type Role struct {
orm.Model
Name string
}對應的表結構:
users
id - 整數
name - 字串
roles
id - 整數
name - 字串
role_user
user_name - 整數
role_name - 整數多態
多態關係允許子模型使用單一關聯屬於多種模型類型。 例如,假設你正在建構一個允許用戶分享博文和視頻的應用程式。 在這樣的應用程式中,Comment 模型可能同時屬於 Post 和 Video 模型。
表結構
多態關係與普通關係相似;然而,子模型可以使用單一關聯屬於多種模型類型。 例如,一篇博客Post和一個User可能與Image模型共享多態關係。 使用多態關係使你可以擁有一個唯一的圖像表,該表可能與帖子和用戶相關聯。 首先,讓我們檢查表結構:
posts
id - 整數
name - 字串
videos
id - 整數
name - 字串
images
id - 整數
url - 字串
imageable_id - 整數
imageable_type - 字串
comments
id - 整數
body - 文本
commentable_id - 整數
commentable_type - 字串注意 images 表的 imageable_id 和 imageable_type 欄位。 imageable_id 欄位將包含文章或用戶的 ID 值,而 imageable_type 欄位將包含父模型的類名。 imageable_type 欄位用於 Orm 確定在訪問 imageable 關係時返回哪一類父模型。 comments 表類似。
模型結構
接下來,讓我們檢查構建此關係所需的模型定義:
type Post struct {
orm.Model
Name string
Image *Image `gorm:"polymorphic:Imageable"`
Comments []*Comment `gorm:"polymorphic:Commentable"`
}
type Video struct {
orm.Model
Name string
Image *Image `gorm:"polymorphic:Imageable"`
Comments []*Comment `gorm:"polymorphic:Commentable"`
}
type Image struct {
orm.Model
Name string
ImageableID uint
ImageableType string
}
type Comment struct {
orm.Model
Name string
CommentableID uint
CommentableType string
}你可以使用 polymorphicValue 標籤來更改多態類型的值,例如:
type Post struct {
orm.Model
Name string
Image *Image `gorm:"polymorphic:Imageable;polymorphicValue:master"`
}查詢關聯
例如,假設有一個博客應用,其 User 模型有許多關聯的 Post 模型:
type User struct {
orm.Model
Name string
Posts []*Post
}
type Post struct {
orm.Model
UserID uint
Name string
}創建或更新關聯
你可以使用 Select, Omit 方法來控制關聯的創建和更新。 這兩個方法不能同時使用,且關聯的控制功能僅適用於 Create, Update, Save:
user := models.User{Name: "user", Posts: []*models.Post{{Name: "post"}}}
// 在創建用戶的同時創建所有子關聯
facades.Orm().Query().Select(orm.Associations).Create(&user)
// 在創建用戶的同時僅創建 Post 子關聯。注意:如果不使用 `orm.Associations`,而是單獨自定義特定子關聯,則此時也應將所有父模型中的欄位列出。
facades.Orm().Query().Select("Name", "Posts").Create(&user)
// 在創建用戶時,忽略 Post,但創建所有其他子關聯
facades.Orm().Query().Omit("Posts").Create(&user)
// 在創建用戶時,忽略 Name 欄位,但創建所有子關聯
facades.Orm().Query().Omit("Name").Create(&user)
// 在創建用戶時,忽略 Name 欄位及所有子關聯
facades.Orm().Query().Omit("Name", orm.Associations).Create(&user)查找關聯
// 查找所有匹配的關聯記錄
var posts []models.Post
facades.Orm().Query().Model(&user).Association("Posts").Find(&posts)
// 查找帶條件的關聯
facades.Orm().Query().Model(&user).Where("name = ?", "goravel").Order("id desc").Association("Posts").Find(&posts)添加關聯
為 manyToMany, hasMany 添加新的關聯;為 hasOne, belongsTo 替換當前的關聯:
facades.Orm().Query().Model(&user).Association("Posts").Append([]*models.Post{Post1, Post2})
facades.Orm().Query().Model(&user).Association("Posts").Append(&models.Post{Name: "goravel"})更改關聯
用新的關聯替換當前的關聯:
facades.Orm().Query().Model(&user).Association("Posts").Replace([]*models.Post{Post1, Post2})
facades.Orm().Query().Model(&user).Association("Posts").Replace(models.Post{Name: "goravel"}, Post2)刪除關聯
如果關聯存在,則刪除父模型與子模型之間的關係,注意,只會刪除引用,不會從資料庫中刪除這些物件,外鍵必須為 NULL:
facades.Orm().Query().Model(&user).Association("Posts").Delete([]*models.Post{Post1, Post2})
facades.Orm().Query().Model(&user).Association("Posts").Delete(Post1, Post2)清空關聯
移除源與關聯之間的所有引用,但不會刪除這些關聯:
facades.Orm().Query().Model(&user).Association("Posts").Clear()關聯計數
返回當前關聯的數量:
facades.Orm().Query().Model(&user).Association("Posts").Count()
// 條件計數
facades.Orm().Query().Model(&user).Where("name = ?", "goravel").Association("Posts").Count()批量處理數據
// 查詢所有用戶的所有文章
facades.Orm().Query().Model(&users).Association("Posts").Find(&posts)
// 從所有 Post 中刪除 user A
facades.Orm().Query().Model(&users).Association("Posts").Delete(&userA)
// 獲取去重的用戶所屬 Post 數量
facades.Orm().Query().Model(&users).Association("Posts").Count()
// 對於批量數據的 `Append`、`Replace`,參數的長度必須與數據的長度相同,否則會返回錯誤
var users = []models.User{user1, user2, user3}
// 有三個用戶,Append userA 到 user1 的 team,Append userB 到 user2 的 team,Append userA、userB 和 userC 到 user3 的 team
facades.Orm().Query().Model(&users).Association("Team").Append(&userA, &userB, &[]models.User{userA, userB, userC})
// 重置 user1 的 team 為 userA,重置 user2 的 team 為 userB,重置 user3 的 team 為 userA、userB 和 userC預加載
預加載為多個模型的查詢提供方便,同時減輕了 "N + 1" 查詢問題。 為了說明 N + 1 查詢問題,考慮一個 Book 模型 "屬於" 一個 Author 模型:
type Author struct {
orm.Model
Name string
}
type Book struct {
orm.Model
AuthorID uint
Name string
Author *Author
}現在,讓我們檢索所有書籍及其作者:
var books []models.Book
facades.Orm().Query().Find(&books)
for _, book := range books {
var author models.Author
facades.Orm().Query().Find(&author, book.AuthorID)
}要檢索資料庫表中的所有書籍及其作者,循環代碼會對每本書執行一次查詢。 這意味著對於 25 本書的集合,循環會執行 26 個查詢 - 一個是為了書籍集合,另外 25 個是用來獲取每本書的作者。
不過,我們可以使用預加載簡化這個過程。 通過使用 With 方法,我們可以指定需要預加載哪些關係,並將查詢數量減少到只有兩個。
var books []models.Book
facades.Orm().Query().With("Author").Find(&books)
for _, book := range books {
fmt.Println(book.Author)
}對於此操作,將只執行兩個查詢 - 一個查詢檢索所有書籍,另一個查詢檢索所有書籍的所有作者:
select * from `books`;
select * from `authors` where `id` in (1, 2, 3, 4, 5, ...);預加載多個關係
有時你可能需要預加載幾個不同的關係。 為了做到這一點,只需多次調用 With 方法:
var book models.Book
facades.Orm().Query().With("Author").With("Publisher").Find(&book)嵌套預加載
要預加載某個關係的關係,你可以使用 "點" 語法。 例如,讓我們預加載所有書籍的作者以及所有作者的個人聯絡人:
var book models.Book
facades.Orm().Query().With("Author.Contacts").Find(&book)限制預加載
有時你可能希望預加載一個關係,同時為預加載查詢指定額外的查詢條件。 你可以如下面所示完成這一點:
import "github.com/goravel/framework/contracts/database/orm"
var book models.Book
facades.Orm().Query().With("Author", "name = ?", "author").Find(&book)
facades.Orm().Query().With("Author", func(query orm.Query) orm.Query {
return query.Where("name = ?", "author")
}).Find(&book)在這個例子中,Orm 只會預加載作者的 name 列等於 author 的書籍。
延遲預加載
有時你可能需要在已檢索到父模型後立即加載關係。 例如,如果你需要動態決定是否加載相關模型,這可能很有用:
var books []models.Book
facades.Orm().Query().Find(&books)
for _, book := range books {
if someCondition {
err := facades.Orm().Query().Load(&book, "Author")
}
}如果你需要對預加載查詢設置額外的查詢條件,可以使用以下代碼:
import "github.com/goravel/framework/contracts/database/orm"
var book models.Book
facades.Orm().Query().Load(&book, "Author", "name = ?", "author").Find(&book)
facades.Orm().Query().Load(&book, "Author", func(query orm.Query) orm.Query {
return query.Where("name = ?", "author")
}).Find(&book)要僅在關係尚未加載時才加載關係,請使用 LoadMissing 方法:
facades.Orm().Query().LoadMissing(&book, "Author")