一、簡單介紹:
當我們設計資料庫時,經常會遇到實體之間的關係。這些關係通常可以分為三種型別:many-to-many(多對多)、many-to-one(多對一)和one-to-many(一對多)。
-
Many-to-Many (多對多):
- 意義:多對多關係表示一個實體可以與多個其他實體相關聯,同時這些實體也可以與多個該實體相關聯。例如,學生和課程之間的關係。一個學生可以選修多門課程,而一門課程也可以被多個學生選修。
- 示例:學生和課程之間的關係,一個學生可以選修多門課程,一門課程也可以被多個學生選修。
-
Many-to-One (多對一):
- 意義:多對一關係表示多個實體可以與一個實體相關聯,但是該實體只能與一個其他實體相關聯。例如,訂單和客戶之間的關係。一個客戶可以有多個訂單,但一個訂單隻能屬於一個客戶。
- 示例:訂單和客戶之間的關係,一個客戶可以有多個訂單,但是一個訂單隻能屬於一個客戶。
-
One-to-Many (一對多):
- 意義:一對多關係表示一個實體可以與多個其他實體相關聯,但是這些實體只能與一個該實體相關聯。例如,部門和員工之間的關係。一個部門可以有多個員工,但一個員工只能屬於一個部門。
- 示例:部門和員工之間的關係,一個部門可以有多個員工,但是一個員工只能屬於一個部門。
這些關係的存在意義在於,它們能夠更好地組織和描述現實世界中的資料關聯。透過正確地建模這些關係,我們可以更有效地查詢、更新和管理資料,並且可以確保資料庫的一致性和完整性。
二、優劣勢對比(相對於直接使用資料庫表結構):
優勢:
-
資料結構更清晰:
- 使用關係模型可以更準確地反映實際世界中的資料關聯,使資料結構更加清晰、直觀。這樣做能夠更好地組織資料,使得資料的邏輯關係更容易理解。
-
減少資料冗餘:
- 在使用關係模型時,可以避免資料冗餘,因為每個實體只需要儲存自己的資訊,而不需要重複儲存相關實體的資訊。這樣可以節省儲存空間,並減少資料更新時的複雜性。
-
更容易維護:
- 使用關係模型可以更容易地維護資料的一致性和完整性。由於資料的邏輯結構更清晰,因此對資料進行更新、刪除和查詢等操作會更加簡單和直觀。
-
支援更復雜的查詢:
- 關係模型使得可以執行更復雜的查詢操作,例如跨多個實體的聯合查詢、多層級的查詢等。這些查詢在直接使用表結構時可能會更加繁瑣和複雜。
-
提高程式碼可讀性和可維護性:
- 透過使用關係模型,可以編寫更具可讀性和可維護性的程式碼,因為程式碼可以更直觀地反映資料之間的關係,降低了開發人員理解和維護程式碼的難度。
儘管使用關係模型可能會增加一些複雜性,但它們通常可以提供更好的資料組織結構和更高的靈活性,從而使得資料庫設計更加健壯和可擴充套件。
劣勢:
-
效能影響:
- 使用關係模型可能會對效能產生一定的影響。因為在執行查詢時,可能需要進行關聯操作,這可能會導致更復雜的查詢語句和更長的查詢時間。尤其是在處理大量資料時,這種影響可能會更加明顯。
-
複雜性增加:
- 關係模型可能會增加資料結構的複雜性,使得資料庫設計和維護變得更加困難。特別是在設計涉及多個實體之間複雜關係的資料庫時,可能需要更多的關注和精力來管理資料模型。
-
資料完整性和一致性的挑戰:
- 使用關係模型可能會增加資料完整性和一致性方面的挑戰。例如,當刪除一個實體時,可能需要考慮到與其關聯的其他實體,以確保資料的一致性。這可能需要更多的程式碼來處理這些情況,增加了開發和維護的複雜性。
-
不適用於簡單場景:
- 對於一些簡單的資料關係,使用關係模型可能會顯得過於複雜。在這種情況下,直接使用資料庫表結構可能會更加簡單和直觀,不需要引入額外的複雜性。
-
學習成本增加:
- 對於開發人員來說,使用關係模型可能需要額外的學習成本。尤其是對於那些沒有使用過ORM(物件關係對映)工具或者不熟悉關聯式資料庫設計的開發人員來說,需要花費更多的時間來理解和掌握這些概念。
儘管關係模型有其劣勢,但在許多情況下,它們仍然是一種有效的資料庫設計方法,特別是在處理複雜的資料關係和需要靈活查詢的情況下。因此,在選擇資料庫設計方案時,需要綜合考慮實際需求、效能要求以及開發團隊的技術水平等因素。
三、GORM具體操作
重點:
1. 在 GORM 中,Association
是一個用於處理模型之間關聯關係的方法。具體來說,Association
方法用於獲取與模型關聯的其他模型的查詢構建器,並提供了一系列方法來操作這些關聯模型。
對於 many-to-many 關係,Association
方法允許你獲取和操作關聯的中間表的查詢構建器。你可以使用 Association
方法來執行以下操作:
Append
:將一個或多個關聯模型新增到中間表中。Replace
:用給定的關聯模型替換中間表中的所有關聯模型。Delete
:從中間表中刪除與給定關聯模型相匹配的記錄。Clear
:清空中間表中所有關聯的記錄。
對於 one-to-many 和 many-to-one 關係,Association
方法類似地允許你獲取和操作關聯的模型的查詢構建器。你可以使用 Association
方法來執行以下操作:
Append
:將一個或多個關聯模型新增到關聯的模型中。Replace
:用給定的關聯模型替換關聯的模型。Delete
:刪除關聯的模型。
透過使用 Association
方法,你可以方便地管理模型之間的關聯關係,執行新增、替換、刪除等操作,而不需要手動編寫 SQL 語句。這使得資料操作更加簡單和直觀。
2. Preload
是一個用於預載入關聯資料的方法。當你查詢一個模型的時候,有時候你需要同時載入關聯的資料,以避免 N+1 查詢問題,即每次查詢主模型都會額外執行一次查詢來獲取關聯模型的資料。
Preload
方法允許你在查詢主模型時預載入關聯模型的資料,從而在一次資料庫查詢中獲取主模型及其關聯模型的資料,而不是在之後的每次訪問關聯模型時執行額外的查詢。
例如,假設你有一個 User
模型,每個使用者有多個 Post
,你想在查詢使用者時預載入使用者的所有帖子資料,你可以使用 Preload
方法:
var user User
db.Preload("Posts").First(&user)
這將會在查詢使用者時預載入使用者的所有帖子資料,以避免之後在訪問使用者的帖子時執行額外的查詢。在這個例子中,假設 User
模型有一個名為 Posts
的關聯關係,用於關聯使用者的帖子。
Preload
方法也可以用於多級關聯關係。例如,如果 Post
模型還有一個名為 Comments
的關聯關係,你可以這樣預載入使用者的帖子以及每個帖子的評論資料:
var user User
db.Preload("Posts").Preload("Posts.Comments").First(&user)
這將會在查詢使用者時預載入使用者的所有帖子以及每個帖子的評論資料。
3. 標籤foreignkey
和 association_foreignkey
是用於定義關聯關係的兩個重要標籤,它們有以下區別:
-
foreignkey:
-
foreignkey
用於指定當前模型在關聯關係中的外來鍵欄位。- 當你在一個模型中定義了一個關聯關係時,通常你需要指定當前模型在關聯關係中的外來鍵欄位,以便 GORM 知道如何在資料庫中建立關聯。
- 例如,在一個多對一關係中,外來鍵欄位通常存在於“多”方,而
foreignkey
用於指定這個外來鍵欄位。
-
association_foreignkey:
-
association_foreignkey
用於指定當前模型關聯的另一個模型在關聯關係中的外來鍵欄位。- 當你在 一個模型中定義了一個關聯關係時,有時候你可能需要指定關聯的另一個模型在關聯關係中的外來鍵欄位,以便 GORM 知道如何在資料庫中建立關聯。
- 例如, 在一個多對一關係中,另一個模型通常為“一”方,而
association_foreignkey
用於指定這個另一個模型在關聯關係中的外來鍵欄位。
-
總之,foreignkey
用於指定當前模型在關聯關係中的外來鍵欄位,而 association_foreignkey
用於指定當前模型關聯的另一個模型在關聯關係中的外來鍵欄位。這兩個標籤的作用是為了告訴 GORM 如何在資料庫中建立關聯。
many2many
1 package main 2 3 import "gorm.io/gorm" 4 5 type User struct { 6 ID uint `gorm:"primaryKey"` 7 Name string 8 Roles []Role `gorm:"many2many:user_roles;association_foreignkey:RoleID;foreignkey:UserID;"` 9 } 10 11 type Role struct { 12 ID uint `gorm:"primaryKey"` 13 Name string 14 Users []User `gorm:"many2many:user_roles;association_foreignkey:UserID;foreignkey:RoleID;"` 15 } 16 17 type UserRole struct { 18 UserID uint 19 RoleID uint 20 } 21 22 // 設定表名稱 23 func (table *User) TableName() string { 24 return "sys_user" 25 } 26 27 // 設定表名稱 28 func (table *Role) TableName() string { 29 return "sys_role" 30 } 31 32 // 設定表名稱 33 func (table *UserRole) TableName() string { 34 return "user_roles" 35 } 36 37 func SaveUserWithRoles(user *User, db *gorm.DB) error { 38 // 如果使用者ID為0,則表示要建立新使用者 39 if user.ID == 0 { 40 if err := db.Create(user).Error; err != nil { 41 return err 42 } 43 } else { // 否則更新使用者記錄 44 if err := db.Save(user).Error; err != nil { 45 return err 46 } 47 } 48 49 // 處理使用者的角色 50 var roleIDs []uint 51 for _, role := range user.Roles { 52 // 如果角色ID為0,則表示要建立新角色 53 if role.ID == 0 { 54 if err := db.Create(&role).Error; err != nil { 55 return err 56 } 57 } else { // 否則更新角色記錄 58 if err := db.Save(&role).Error; err != nil { 59 return err 60 } 61 } 62 roleIDs = append(roleIDs, role.ID) 63 } 64 65 // 替換使用者的角色為最新的角色列表 66 if err := db.Model(user).Association("Roles").Replace(user.Roles); err != nil { 67 return err 68 } 69 70 return nil 71 }
many2one one2many
1 package main 2 3 // User 和 Address,一個使用者可以有多個地址,但是每個地址只屬於一個使用者。同時,我們還有一個 Location 模型,地址可以關聯到一個地點。在這種情況下,我們可以使用 foreignKey 和 association_foreignkey 來指定外來鍵。 4 // 在這個例子中,我們在 Address 模型中同時使用了 foreignKey 和 association_foreignkey。foreignKey:UserID 指定了關聯到 User 模型的外來鍵,而 association_foreignkey:LocationID 指定了關聯到 Location 模型的外來鍵。 5 type User struct { 6 ID uint `gorm:"primaryKey"` 7 Name string 8 Addresses []Address `gorm:"foreignKey:UserID"` 9 } 10 11 type Address struct { 12 ID uint `gorm:"primaryKey"` 13 Street string 14 UserID uint // 外來鍵,用於關聯到 User 模型。該欄位型別必須和User中的ID欄位型別一致 15 LocationID uint // 地點ID,外來鍵,用於關聯到 Location 模型 16 Location Location `gorm:"foreignKey:LocationID"` 17 } 18 19 type Location struct { 20 ID uint `gorm:"primaryKey"` 21 City string 22 } 23 24 25 // 建立一個新使用者 26 user := User{Name: "Alice"} 27 28 // 建立使用者的地址 29 address1 := Address{Street: "123 Main St", UserID: user.ID} 30 address2 := Address{Street: "456 Elm St", UserID: user.ID} 31 32 // 建立地址的地點 33 location := Location{City: "New York"} 34 35 // 儲存地點到資料庫 36 db.Create(&location) 37 38 // 設定地址的地點ID 39 address1.LocationID = location.ID 40 address2.LocationID = location.ID 41 42 // 儲存使用者的地址到資料庫 43 db.Create(&address1) 44 db.Create(&address2) 45 46 // 儲存使用者到資料庫 47 if err := db.Create(&user).Error; err != nil { 48 // 處理錯誤 49 }