實(shí)體繼承就是我們平時(shí)編程時(shí)用到的類(lèi)的繼承捶枢。不過(guò)由于ORM牽扯到數(shù)據(jù)在數(shù)據(jù)庫(kù)中的存儲(chǔ)問(wèn)題寝优,所以情況要復(fù)雜一些玩裙,常見(jiàn)的對(duì)于有繼承關(guān)系的類(lèi)處理方法一般是以下三種:
- 單表繼承模式 父類(lèi)和子類(lèi)的所有字段都保存在同一張表里。
- 具體表繼承模式 父類(lèi)和子類(lèi)的所有字段分別保存在自己的表里
- 聯(lián)表繼承模式 父類(lèi)的表記錄公共字段帝火,后代類(lèi)的表記錄自己的專用字段
django使用的是聯(lián)表繼承模式痕貌,sqlalchemy作為重量級(jí)的ORM實(shí)現(xiàn)了全部的3種繼承模式(牛逼胺缯帧!??????)舵稠,而pony選擇的是單表繼承模式超升。三種模式的優(yōu)缺點(diǎn)如下:
單表繼承模式
優(yōu)點(diǎn):
- 由于所有的記錄都放在一張表里,所以查詢對(duì)象的時(shí)候哺徊,無(wú)需聯(lián)表查詢室琢,速度快
- 有關(guān)系的父子類(lèi)之間不存在數(shù)據(jù)同步更新問(wèn)題。
- 冗余字段少落追。
缺點(diǎn):
- 不同類(lèi)的數(shù)據(jù)保存在一起盈滴,不方便剝離某個(gè)單獨(dú)的類(lèi)。
- 由于不同的子類(lèi)可能有不同的字段定義轿钠,導(dǎo)致表中出現(xiàn)大量的Null值巢钓,雖然這并不會(huì)占用大量的空間病苗,但是對(duì)于某些類(lèi)型的強(qiáng)迫癥患者來(lái)說(shuō),這可能是一個(gè)不能忍的缺陷症汹。
- 由于所有的類(lèi)的字段的定義都保存在一張表內(nèi)硫朦,在某種極端的情況下,這可能會(huì)達(dá)到數(shù)據(jù)庫(kù)的限制烈菌。不過(guò)一般情況下阵幸,這種限制都很高。比如mysql的最大列限制是4096芽世,最大行尺寸是65,535bytes
具體表繼承模式
優(yōu)點(diǎn):
- 不同的類(lèi)都有自己專用的表,無(wú)需聯(lián)表查詢诡壁,速度快济瓢。
- 每個(gè)類(lèi)的數(shù)據(jù)獨(dú)立存儲(chǔ)自己的表中,方便剝離妹卿, 靈活性高旺矾。
缺點(diǎn):
- 冗余字段多,浪費(fèi)空間夺克。
- 有關(guān)系的父子類(lèi)之間可能存在數(shù)據(jù)同步更新問(wèn)題箕宙。(依模型設(shè)計(jì)而定),要小心處理铺纽。
聯(lián)表繼承模式
優(yōu)點(diǎn):
- 冗余字段少且沒(méi)有額外的Null值
- 有關(guān)系的父子類(lèi)之間不存在數(shù)據(jù)同步更新問(wèn)題柬帕。
缺點(diǎn):
- 子類(lèi)的數(shù)據(jù)需要聯(lián)表查詢,效率低狡门,這也是這種模式最大的問(wèn)題陷寝。
- 不同類(lèi)的數(shù)據(jù)雖然保存在不同的表中,但由于每個(gè)表保存的都是部分?jǐn)?shù)據(jù)所以依然不方便剝離某個(gè)單獨(dú)的類(lèi)其馏。
演示一下
class Person(db.Entity):
"""人"""
_table_ = "person"
name = Required(str, max_len=40)
# class_type = Discriminator(str) # 定義一個(gè)鑒別器字段
class Customer(Person):
"""顧客"""
member_level = Required(int, size=8, default=0) # 會(huì)員卡級(jí)別
# _discriminator_ = "customer" 鑒別標(biāo)識(shí)
class Sales(Person):
"""銷(xiāo)售員"""
performance = Required(float, default=0.0) # 銷(xiāo)售業(yè)績(jī)
# _discriminator_ = "sales" 鑒別標(biāo)識(shí)
if __name__ == "__main__":
db.drop_table(table_name="person", if_exists=True, with_all_data=True) # 刪除表凤跑,演示實(shí)體聲明時(shí)用于快速清除舊表
db.generate_mapping(create_tables=True) # 生成實(shí)體,表和映射關(guān)系
with db_session:
person = Person(name="約翰") # 創(chuàng)建一個(gè)Person對(duì)象的實(shí)例
customer = Customer(name="瑪麗", member_level=1) # 創(chuàng)建一個(gè)Customer對(duì)象的實(shí)例
sales = Sales(name="喬治", performance=3000) # 創(chuàng)建一個(gè)Sales對(duì)象的實(shí)例
flush() # 刷新叛复,這里的刷新是為了讓剛才創(chuàng)建的對(duì)象分配到id
print("所有人員信息:" + " ".join([x for x in select(x.name for x in Person)]))
print("所有客戶信息:" + " ".join([x for x in select(x.name for x in Customer)]))
print("所有銷(xiāo)售員信息:" + " ".join([x for x in select(x.name for x in Sales)]))
pass
說(shuō)明
- 只能在頂級(jí)的父類(lèi)中定義表名仔引,子類(lèi)表中不可重新定義表名
- 父類(lèi)中可以自定義一個(gè)鑒別器字段,用于區(qū)別不同的類(lèi)褐奥,定義的方法上面代碼咖耘,要求是
只要是Discriminator的實(shí)例就好了,可以是字符串抖僵,也可以是其他類(lèi)型鲤看,不過(guò),當(dāng)不是字符串的時(shí)候耍群,子類(lèi)必須定義discriminator字段(鑒別標(biāo)識(shí)义桂,用于確認(rèn)類(lèi)的數(shù)據(jù)在表中的唯一性)找筝,鑒別器字段不是必須定義的,系統(tǒng)會(huì)提供默認(rèn)值classtype - 子類(lèi)中可以定義discriminator(鑒別標(biāo)識(shí))字段慷吊,用于確認(rèn)字段的歸屬袖裕。定義時(shí),必須和頂級(jí)父類(lèi)中定義的Discriminator實(shí)例的類(lèi)型一致,鑒別標(biāo)識(shí)字段不是必須定義的溉瓶,系統(tǒng)會(huì)提把類(lèi)名作為默認(rèn)值急鳄。
執(zhí)行結(jié)果
Connected to pydev debugger (build 192.6603.34)
所有人員信息:約翰 瑪麗 喬治
所有客戶信息:瑪麗
所有銷(xiāo)售員信息:?jiǎn)讨?
Process finished with exit code 0
你可以注意到,由于Customer和Sales是Person的子類(lèi)堰酿,所以在添加Customer和Sales實(shí)例時(shí)疾宏,Person也增加了對(duì)應(yīng)的實(shí)例
請(qǐng)注意classtype是鑒別器字段,這里使用了默認(rèn)值類(lèi)名作鑒別標(biāo)識(shí)触创。