zoukankan      html  css  js  c++  java
  • 深入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语句,可以验证继承的实现机制,避免带来意想不到的问题。

    本人已在github上用Jekyll建立了新的博客:http://thinkinside.tk/,本站文章会陆续迁移过去


  • 相关阅读:
    monkey之monkey日志分析
    ros 阅读记录 1
    Occupancy Grid Maps 资料记录
    双目稠密点云的资料记录
    ros 在callback中publish (不用类的方法)
    ros中同时订阅两个topic(2张图像)合并成一个topic(1张图像)
    编译 链接和加载 (转)
    制作自己的livecd
    源码安装ROS Melodic Python3 指南 (转) + 安装记录
    Depth from Videos in the Wild 解读
  • 原文地址:https://www.cnblogs.com/holbrook/p/2405036.html
Copyright © 2011-2022 走看看