文章目錄
- JOINED映射
- TABLE_PER_CLASS映射
- SINGLE_TABLE映射
- 代理模型
- 多重繼承
- 小結
ORM中通常將對象引用映射到外鍵,但是對於繼承,關聯式資料庫中沒有自然有效方法來對應。從資料存放區的角度來看,在映射繼承關係時,可以採用幾種方式(參考JPA中的InheritanceType.定義):
- 使用單個表,在JPA中稱作SINGLE_TABLE。整個繼承樹共用一張表。使用唯一的表,包含所有基類和子類的欄位。
- 每個具體類一張表,在JPA中稱作TABLE_PER_CLASS。這種方式下,每張表都包含具體類和繼承樹上所有父類的欄位。因為多個表中有重複欄位,從整個繼承樹上來說,欄位是冗餘的。
- 每個類一張表,繼承關係通過表的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語句,可以驗證繼承的實現機制,避免帶來意想不到的問題。