什么是polymorphic

熟悉SQLAlchemy的人往往知道polymorphic(多态)的模型定义,如果你恰好不熟悉SQLAlchemy,这里简单的举一个例子:

class Employee(Base):  __tablename__ = 'employee'id = Column(Integer, primary_key=True)name = Column(String(50))type = Column(String(50))__mapper_args__ = {'polymorphic_identity':'employee','polymorphic_on':type}

这里定义了雇员Employee 模型,指定type字段为多态所在字段,并且对于这个模型,当type字段为'employee'时,即为一个雇员

再看下面这两个继承之后的模型

class Engineer(Employee):  __tablename__ = 'engineer'id = Column(Integer, ForeignKey('employee.id'), primary_key=True)engineer_name = Column(String(30))__mapper_args__ = {'polymorphic_identity':'engineer',}class Manager(Employee):  __tablename__ = 'manager'id = Column(Integer, ForeignKey('employee.id'), primary_key=True)manager_name = Column(String(30))__mapper_args__ = {'polymorphic_identity':'manager',}

这里又定义了两个模型,Engineer,Manager,

并对应了两张表,这两张表的结构除了有少许不同,类似的,polymorphic_identity指定了这两种模型对应的type字段值,

在上面的基础上,可以提出的问题:

  1. 可不可以完全在一张表上实现这样的多态?
  2. 这样的模型可以用作M2M关系吗?

两者的答案显然是肯定的。 对于第一个问题,只需要使得后两者的__tablename__ = None,并且不指定额外的字段即可。

第二个问题,即这几天我的重构的探索

如何设置多对多模型

对于一个多对多的关系表,按照 SQLAlchemy 文档:

association_table = Table('association', Base.metadata,  Column('left_id', Integer, ForeignKey('left.id')),Column('right_id', Integer, ForeignKey('right.id'))
)class Parent(Base):  __tablename__ = 'left'id = Column(Integer, primary_key=True)children = relationship("Child",secondary=association_table)class Child(Base):  __tablename__ = 'right'id = Column(Integer, primary_key=True)

(虽然我们的基本不会按照SQLAlchemy那样定义ForeignKey了,万恶的ForeignKey。。)

关键在于应当有第三张表,存放M2M的关系。上面的association,就是这样的一张M2M表,有两个字段left_id和right_id

而且显然的,我们可以轻松地想象出取出M2M关系的SQL:

select left.id,right.id from left join association on left.id=association.left_id
join right on association.right_id=right.id

是借助association实现两个表的JOIN关系

SQLAlchemy 的对应操作这里就不赘述了,大家看文档吧咩哈哈

M2M和多态

此次重构遇到的问题就是:如果我们的M2M的关系,如果是在多态上进行的,例如上面的Child,如果我不仅仅有Child,还分Boy和Girl,如何在这一张association_table进行控制呢? 上面代码先稍作修改:

class Association(Base):  left_id = Column('left_id', Integer, ForeignKey('left.id')),right_id = Column('right_id', Integer, ForeignKey('right.id'))gender = Column('gender', Boolean)__mapper_args__ = {"polymorphic_on": gender}

增加了gender字段,并且增加了多态声明__mapper_args__ 我们先假设一下这样的SQL该怎么写吧,实际上是很简单的哈:

select left.id,right.id from left join association on (left.id=association.left_id and association.gender)
join right on association.right_id=right.id

join的时候额外加一个字段即可。

如何让SQLalchemy可以生成出这样的SQL,并且还自动进行例如增删查改的SQL声明呢?

SQLAlchemy同样给出了对应的 样例

我基于这个样例做了一定的修改:

  1. 完全不用ForeignKey声明。这一点很容易,from sqlalchemy.orm import foreign,用foreign函数包一下对应的字段,就可以当成外键来用
  2. 样例中的Address声明了association = relationship("AddressAssociation", backref="addresses"),这样使得AddressAssociation有了一个addresses的反向引用(backref),在实际的M2M模型设计中,考虑到是跨模块的模型映射,为了方便修改和维护,没有修改M2M左边的这个M,因此在AddressAssociation中动态声明了一个addresses
  3. 增加relationship cascade属性,以进行删除操作

对应的diff如下(稍微修改了字段名),稍后有完整代码:

--- origin.py    2016-10-13 11:28:57.000000000 +0800
+++ target.py    2016-10-13 11:29:44.000000000 +0800
@@ -1,80 +1,84 @@  from sqlalchemy.ext.declarative import as_declarative, declared_attrfrom sqlalchemy import create_engine, Integer, Column, \
-    String, ForeignKey
-from sqlalchemy.orm import Session, relationship, backref
+    String, and_
+from sqlalchemy.orm import Session, foreign, relationship, backref  from sqlalchemy.ext.associationproxy import association_proxyclass AddressAssociation(Base):"""Associates a collection of Address objectswith a particular parent."""__tablename__ = "address_association"
-
-    discriminator = Column(String)
+    addr_id = Column(Integer,
+                     primary_key=True,
+                     )
+    order_id = Column(Integer,
+                      primary_key=True,
+                      )
+    discriminator = Column(String, primary_key=True)  """Refers to the type of parent."""__mapper_args__ = {"polymorphic_on": discriminator}class Address(Base):"""The Address class.This represents all address records in asingle table."""
-    association_id = Column(Integer, ForeignKey("address_association.id"))
+    id = Column(Integer, primary_key=True)  street = Column(String)city = Column(String)zip = Column(String)
-    association = relationship("AddressAssociation", backref="addresses")
-
-    parent = association_proxy("association", "parent")def __repr__(self):return "%s(street=%r, city=%r, zip=%r)" % \(self.__class__.__name__, self.street,self.city, self.zip)class HasAddresses(object):"""HasAddresses mixin, creates a relationship tothe address_association table for each parent."""@declared_attr
-    def address_association_id(cls):
-        return Column(Integer, ForeignKey("address_association.id"))
-
-    @declared_attr  def address_association(cls):name = cls.__name__discriminator = name.lower()assoc_cls = type("%sAddressAssociation" % name,(AddressAssociation, ),dict(__tablename__=None,__mapper_args__={"polymorphic_identity": discriminator
-                }
-            )
+                },
+                addresses=relationship(
+                    Address,
+                    primaryjoin="Address.idforeign({assoc_cls_name}.addr_id)".format(assoc_cls_name=assoc_cls_name))
+                        )  )cls.addresses = association_proxy("address_association", "addresses",creator=lambda addresses: assoc_cls(addresses=addresses))return relationship(assoc_cls,
-                            backref=backref("parent", uselist=False))
+                            primaryjoin=and_(foreign(assoc_cls.addr_id)  Address.id,
+                                             foreign(assoc_cls.order_id) == cls.id),
+                            cascade="save-update, merge, delete, delete-orphan",
+                            )class Customer(HasAddresses, Base):name = Column(String)class Supplier(HasAddresses, Base):company_name = Column(String)

此后就可以通过Customer.addresses.append等操作M2M了

SQLAlchemy 多态进阶(__mapper_args__ )、多对多标签相关推荐

  1. sqlalchemy表关系之多对多

    sqlalchemy表之间的关系有三种:1.多对多  2.一对多  3.一对一 下面就讲讲sqlalchemy表之间如何建立多对多关系. 首先,我们把两个需要做多对多关系的模型定义出来,这里以Arct ...

  2. SQLAlchemy 教程 —— 进阶篇

    使用 首先是连接到数据库,SQLALchemy支持多个数据库引擎,不同的数据库引擎连接字符串不一样,常用的有 mysql://username:password@hostname/database p ...

  3. 【Shader进阶】SubShader块标签Tags——IgnoreProjector

    目录 一.创建空物体,挂载Projector,并赋值Material(作用:将Projector范围内的物体材质替换为Projector的Material) 二.我们的主角(带有忽略Projector ...

  4. 第1关:封装、继承和多态进阶(一)

    测试说明 测试输入: 泰迪 male brown 波斯猫 male 2.5 预期输出: 名称:泰迪,性别:male,颜色:brown,汪汪叫 泰迪吃骨头! 名称:波斯猫,性别:male,体重:2.5k ...

  5. 【Hibernate步步为营】--关联映射之多对一

    上篇文章讨论了Hibernate的基本映射,一个实体类对应着一张表,在相应的Hibernate Mapping文件中使用<class>标签映射.并且实体类中的普通属性对应着表字段,使用&l ...

  6. python之SQLAlchemy ORM

    前言: 这篇博客主要介绍下SQLAlchemy及基本操作,写完后有空做个堡垒机小项目.有兴趣可看下python之数据库(mysql)操作.下篇博客整理写篇关于Web框架和django基础~~ 一.OR ...

  7. Mybatis的where标签,还有这么多知识点

    背景 在上篇文章,我们系统地学习了where 1=1 相关的知识点,大家可以回看<不要再用where 1=1了!有更好的写法!>这篇文章.文章中涉及到了Mybatis的替代方案,有好学的朋 ...

  8. 【Android春招每日一练】(十六) 剑指4题+Android进阶

    文章目录 概览 剑指offer 1.61 翻转单词顺序 1.62 左旋转字符串 1.63 滑动窗口的最大值 1.64 队列的最大值 Android进阶 Android布局优化 Android权限处理 ...

  9. 头歌实践实践教学平台:Java面向对象 - 封装、继承和多态的综合练习

    第1关:封装.继承和多态进阶(一) 任务描述 本关任务:按要求编写一个Java应用程序,巩固Java面向对象知识. 相关知识 为了完成本关任务,我们回顾一下前面所学知识:1.面向对象思想 :2.封装: ...

最新文章

  1. Loadrunner 性能测试服务器监控指标
  2. @RequestBody, @ResponseBody 注解详解
  3. 快速入门 Nginx,这篇就够了!
  4. vim trick之 vimrc更改立即生效
  5. 直播 | 天津大学副教授张长青:多模态融合的基础问题及算法研究
  6. [react] 在React中你有遇到过安全问题吗?怎么解决?
  7. 改变JavaScript代码行的背景色
  8. 《梦幻西游》打响反盗号战役:为2亿玩家提供360安全武器
  9. 生成可编辑的pdf(可java代码动态赋值)
  10. Python实战之12306抢票
  11. VMWare IOS MAC分区教程
  12. 字符串与16进制之间的转换
  13. 如何构建基于数字孪生的智慧全息路口
  14. OpenJudge Tian Ji -- The Horse Racing
  15. 数字图像处理学习笔记(十五)——图像复原与重建
  16. 欧拉公式-python程序-计算机仿真方法(入门级)
  17. Git使用-git init
  18. 在英特尔硬件上部署深度学习模型的无代码方法 OpenVINO 深度学习工作台的三部分系列文章 - CPU AI 第一部
  19. MTK5G模块芯片MTK6873_MT6873数据手册/datasheet/规格书
  20. 【知识学习】简易OI/ACM竞赛测试环境lemon使用方法

热门文章

  1. M1兼容性怎么样?关于M1版MacBook兼容软件的测试方法
  2. 提高篇 第三部分 图论 第1章 最小生成树
  3. 1.6 编程基础之一维数组 11 大整数减法
  4. 1.4编程基础之逻辑表达式与条件分支 05 整数大小比较
  5. 华为 HarmonyOS2.0(鸿蒙OS) 开发者beta公测招募的报名流程
  6. php通过使用curl获取http或者https的响应信息的方式
  7. 【ES9(2018)】String 扩展 标签模板里字符串转义
  8. dedecms二次开发常用代码
  9. PHP笔记-连接MySQL数据库及查询数据
  10. C++笔记-二维棋盘数组使用BFS(宽度优先遍历)