深入Django ORM的繼承關系


ORM中通常將對象引用映射到外鍵,但是對於繼承,關系數據庫中沒有自然有效的方法來對應。從數據存儲的角度來看,在映射繼承關系時,可以采用幾種方式(參考JPA中的InheritanceType.定義):

  1. 使用單個表,在JPA中稱作SINGLE_TABLE。整個繼承樹共用一張表。使用唯一的表,包含所有基類和子類的字段。
  2. 每個具體類一張表,在JPA中稱作TABLE_PER_CLASS。這種方式下,每張表都包含具體類和繼承樹上所有父類的字段。因為多個表中有重復字段,從整個繼承樹上來說,字段是冗余的。
  3. 每個類一張表,繼承關系通過表的JOIN操作來表示。在JPA中稱作JOINED。這種方式下,每個表只包含類中定義的字段,不存在字段冗余,但是要同時操作子類和所有父類所對應的表。

Django的ORM也支持上述三種繼承策略,同時,得益於python的動態特性,還支持代理模型和多重繼承關系的映射。

JOINED映射

如果在Django中實現了Model的繼承關系,如下:

from django.db import models

class Person(models.Model):
name = models.CharField(maxlength=10)

class Man(Person):
job = models.CharField(maxlength=20)

class Woman(Person):
makeup = models.CharField(maxlength=20)

則使用manage.py執行sqlall命令時,會看到這樣的結果:

CREATE TABLE "uom_person" (
"id" integer NOT NULL PRIMARY KEY,
"name" varchar(10) NOT NULL
)
;

CREATE TABLE "uom_man" (
"person_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "uom_person" ("id"),
"job" varchar(20) NOT NULL
)
;

CREATE TABLE "uom_woman" (
"person_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "uom_person" ("id"),
"makeup" varchar(20) NOT NULL
)
;

可見,Django ORM中默認使用JOINED方式來實現繼承關系的映射。

TABLE_PER_CLASS映射

如果要實現每個具體類一張表,只需要將父類指定為抽象類(abstract),這樣就不會創建父類對應的表,而將父類的字段復制到子類中去映射。如下:

from django.db import models

class Person(models.Model):
name = models.CharField(max_length=10)

class Meta:
abstract = True

class Man(Person):
job = models.CharField(max_length=20)

class Woman(Person):
makeup = models.CharField(max_length=20)

sqlall 的結果:

CREATE TABLE "uom_man" (
"id" integer NOT NULL PRIMARY KEY,
"name" varchar(10) NOT NULL,
"job" varchar(20) NOT NULL
)
;

CREATE TABLE "uom_woman" (
"id" integer NOT NULL PRIMARY KEY,
"name" varchar(10) NOT NULL,
"makeup" varchar(20) NOT NULL
)
;

將父類聲明為abstract時,該類將沒有objects屬性,也就是說沒有Manager方法,所有無法進行數據操作,只有子類才能進行。

SINGLE_TABLE映射

在TABLE_PER_CLASS的基礎上,如果進一步指定子類的映射表名與父類的相同,則子類和父類將映射到同一張表,對所有的子類都這樣指定,就可以實現SINGLE—_TABLE映射:

from django.db import models

class Person(models.Model):
name = models.CharField(max_length=10)
class Meta:
abstract = True

class Man(Person):
job = models.CharField(max_length=20)
class Meta:
db_table = 'oum_person'

class Woman(User):
makeup = models.CharField(max_length=20)

sqlall 的結果:

CREATE TABLE "oum_person" (
"id" integer NOT NULL PRIMARY KEY,
"name" varchar(10) NOT NULL,
"job" varchar(20) NOT NULL
)
;

CREATE TABLE "uom_woman" (
"user_ptr_id" integer NOT NULL PRIMARY KEY,
"makeup" varchar(20) NOT NULL
)
;

上面的例子中只指定了一個子類,可以看出因為是在子類上指定,所以Django ORM更加靈活,可以控制單個子類的映射方式,從而實現任意的映射結構。

代理模型

有這樣一種常見的場景:使用某些庫(lib)中的類,只是想擴展一些方法,而不想改變其數據存儲結構。在Python中,可以通過在Meta類中增加約束proxy=True來實現。此時“子類”稱為“父類”的代理類,子類中只能增加方法,而不能增加屬性。比如上面的例子中,如果希望Person繼承Django自帶的User類,又不希望破壞User類的數據存儲,則可以指定Person的proxy=True:

from django.db import models
from django.contrib.auth.models import User

class Person(User):
# name = models.CharField(max_length=10)
class Meta:
proxy = True

def do_something(self):
...

class Man(Person):
job = models.CharField(max_length=20)

class Woman(Person):
makeup = models.CharField(max_length=20)

sqlall的結果為:

CREATE TABLE "uom_man" (
"user_ptr_id" integer NOT NULL PRIMARY KEY,
"job" varchar(20) NOT NULL
)
;

CREATE TABLE "uom_woman" (
"user_ptr_id" integer NOT NULL PRIMARY KEY,
"makeup" varchar(20) NOT NULL
)
;

多重繼承

python支持多重繼承,盡管在Model層不推薦使用多重繼承,但Django的ORM還是支持這樣的使用方式:

class Mixin1(models.Model):
attr1 = models.CharField(max_length=10)
class Mixin2(models.Model):
attr1 = models.CharField(max_length=10)
class Multiple(Mixin1,Mixin2):
attr3 = models.CharField(max_length=10)

sqlall的結果是:

CREATE TABLE "uom_mixin1" (
"id" integer NOT NULL PRIMARY KEY,
"attr1" varchar(10) NOT NULL
)
;

CREATE TABLE "uom_mixin2" (
"id" integer NOT NULL PRIMARY KEY,
"attr1" varchar(10) NOT NULL
)
;

CREATE TABLE "uom_multiple" (
"mixin2_ptr_id" integer NOT NULL UNIQUE REFERENCES "uom_mixin2" ("id"),
"mixin1_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "uom_mixin1" ("id"),
"attr3" varchar(10) NOT NULL
)
;

多重繼承的時候,子類的ORM映射會選擇第一個父類作為主鍵管理,其他的父類作為一般的外鍵管理。

小結

Django ORM在映射繼承關系時非常靈活,不僅能夠實現JPA約定的SINGLE_TABLE、TABLE_PER_CLASS、JOINED三種方式,還可以靈活的自定義;甚至通過python的動態語言特性,支持代理模型和多重繼承的功能。但是正因為靈活,所以在使用的時候一定要非常注意,通過manage.py的sqllall功能,觀察產生的sql語句,可以驗證繼承的實現機制,避免帶來意想不到的問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM