ReferenceField、EmbeddedDocumentField和LazyReferenceField的使用和總結對比

XieBuWan發表於2024-11-18

1. ReferenceField

  • 功能:用於在一個文件中引用另一個文件,類似於關係型資料庫中的外來鍵。
  • 儲存方式:儲存被引用文件的 ObjectId
  • 查詢行為:當訪問該欄位時,直接載入被引用的目標文件。
  • 適用場景:適合用於多對一關係或文件之間有獨立管理需求的情況。

特點

  1. 引用的目標文件儲存在單獨的集合中。
  2. 目標文件獨立存在,可以被其他文件引用。
  3. 訪問引用欄位時會觸發資料庫查詢,從而獲取目標文件的資料。

示例

from mongoengine import Document, StringField, ReferenceField

class Author(Document):
    name = StringField()

class Book(Document):
    title = StringField()
    author = ReferenceField(Author)  # 引用 Author 文件

使用:

# 建立引用關係
author = Author(name="John Doe").save()
book = Book(title="My Book", author=author).save()

# 獲取資料
print(book.author.name)  # 訪問時查詢 Author 集合,輸出 "John Doe"

2. EmbeddedDocumentField

  • 功能:用於在文件中巢狀另一個文件,直接將巢狀文件作為欄位儲存在父文件中。
  • 儲存方式:巢狀文件資料直接儲存在父文件的 BSON 中。
  • 查詢行為:父文件查詢時,巢狀文件的資料會隨父文件一起返回。
  • 適用場景:適用於一對一或一對多關係,且巢狀文件與父文件緊密相關,不需要獨立查詢。

特點

  1. 巢狀文件的資料是父文件的一部分,查詢父文件時自動返回巢狀資料。
  2. 巢狀文件不能被其他文件引用。
  3. 文件大小可能變大,適合子文件較小的場景。

示例

from mongoengine import Document, StringField, EmbeddedDocument, EmbeddedDocumentField

class Address(EmbeddedDocument):
    city = StringField()
    street = StringField()

class User(Document):
    name = StringField()
    address = EmbeddedDocumentField(Address)  # 巢狀 Address 文件

使用:

# 巢狀文件資料儲存
address = Address(city="New York", street="5th Avenue")
user = User(name="Alice", address=address).save()

# 獲取資料
print(user.address.city)  # 輸出 "New York"

3. LazyReferenceField

  • 功能:與 ReferenceField 類似,但支援延遲載入,被引用的目標文件只有在訪問時才會觸發查詢。
  • 儲存方式:儲存被引用文件的 ObjectId
  • 查詢行為:當訪問該欄位時,延遲載入目標文件資料。
  • 適用場景:適合需要引用其他文件,但在大部分情況下不訪問引用文件的場景,最佳化效能。

特點

  1. 引用目標文件的方式與 ReferenceField 相同,但支援延遲載入。
  2. 訪問引用欄位時會觸發查詢;如果不訪問,則不會載入目標文件資料。
  3. 提升效能,減少資料庫查詢次數。

示例

from mongoengine import Document, StringField, LazyReferenceField

class Author(Document):
    name = StringField()

class Book(Document):
    title = StringField()
    author = LazyReferenceField(Author)  # 延遲載入 Author 文件

使用:

# 建立引用關係
author = Author(name="Jane Doe").save()
book = Book(title="Another Book", author=author).save()

# 延遲載入目標文件
print(book.author.fetch().name)  # fetch() 查詢 Author 文件,輸出 "Jane Doe"

三者比較總結

特性 ReferenceField EmbeddedDocumentField LazyReferenceField
儲存方式 儲存目標文件的 ObjectId 直接儲存巢狀文件的資料 儲存目標文件的 ObjectId
查詢行為 查詢時直接載入目標文件 查詢父文件時直接返回巢狀資料 延遲載入目標文件,只有在訪問時才查詢目標文件
效能 查詢時需要額外載入目標文件 查詢時目標文件資料已包含在父文件中 查詢時不會載入目標文件,訪問時才觸發查詢
適用關係 一對一、多對一 一對一、一對多 一對一、多對一
資料耦合性 父文件和目標文件獨立,松耦合 父文件和巢狀文件緊密耦合 父文件和目標文件獨立,松耦合
文件大小 文件大小較小,引用目標文件資料 文件大小可能較大,巢狀文件資料增加父文件大小 文件大小較小,引用目標文件資料
適用場景 - 目標文件需要獨立管理 - 資料訪問較頻繁 - 父文件與子文件總是一起操作 - 資料巢狀結構較小 - 延遲訪問目標文件 - 最佳化效能,減少查詢次數

推薦使用場景

  1. ReferenceField
    • 目標文件獨立且可能被多次引用。
    • 資料需要在多個地方管理,不適合巢狀儲存的場景。
    • 例如:使用者(User)和文章(Article)之間的關係。
  2. EmbeddedDocumentField
    • 父文件與子文件緊密關聯,通常一起使用。
    • 資料結構較小且耦合緊密,適合一對一、一對多的巢狀儲存。
    • 例如:使用者(User)和地址(Address)的關係。
  3. LazyReferenceField
    • 引用文件資料較大,訪問不頻繁,適合延遲載入。
    • 需要減少資料庫查詢時的效能最佳化場景。
    • 例如:訂單(Order)引用的使用者(User)資訊,但訂單查詢中不常用使用者詳情。

相關文章