Skip to content

第 10 章 常见的映射方式 ​

从一个生活场景说起 ​

把"Python 类"和"数据库表"绑定在一起,有点像给房间配钥匙。你可以:

  • 按图纸盖房配钥匙:先写类、再生成表(Declarative)——新房装修,主流做法;
  • 先各有图纸,最后配一把通用钥匙:类和表分开定义,最后绑定(Imperative)——老房改造;
  • 拿着房本去现场测绘出图纸:数据库里已经有表了,反向读出结构(Reflection)——接手二手房;
  • 一套钥匙开一层楼:一个类的继承体系共用一张表(继承映射)——复式结构。

本章的任务不是让你全都掌握,而是知道有这几条路、各自什么时候走。日常开发 95% 的场景用第一种就够了。

核心概念 ​

SQLAlchemy 支持多种"类 ↔ 表"的映射方式,初学者需要了解它们的存在和适用场景。

1. Declarative 映射(最常用,本教程全程使用) ​

python
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

class Base(DeclarativeBase): ...

class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)

特点:类定义本身就完整描述了表结构,"代码即文档"。新项目无脑选它。

2. 混合映射(Imperative / Classical) ​

类与表定义分离,用 registry.map_imperatively() 绑定:

python
from sqlalchemy import MetaData, Table, Column, Integer, String
from sqlalchemy.orm import registry

mapper_registry = registry()
metadata = MetaData()

# 先单独定义表
user_table = Table(
    "user", metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
)

# 再单独定义一个"干干净净"的普通类
class User:
    pass

# 最后把两者绑在一起
mapper_registry.map_imperatively(User, user_table)

特点:User 类完全不依赖 SQLAlchemy,可以保持"纯净"。适合已有大量 Table 定义的旧代码,或者领域模型不想被 ORM 框架"污染"的架构(如 DDD 风格)。

3. 从已有数据库反射(Reflection) ​

不写模型,直接从数据库读取表结构:

python
from sqlalchemy import create_engine, MetaData

engine = create_engine("mysql+pymysql://root:123456@localhost:3306/demo_db?charset=utf8mb4")

metadata = MetaData()
metadata.reflect(bind=engine)          # 连接数据库,把所有表结构"测绘"出来
old_table = metadata.tables["user"]    # 直接使用

# 配合 ORM 使用
from sqlalchemy.orm import Session, declarative_base

Base = declarative_base()

class User(Base):
    __table__ = old_table              # 直接绑定反射出来的表

特点:接手一个别人建好的数据库时特别有用——不用手写几十张表的定义,一行 reflect() 全读出来。适合快速探索数据、写一次性脚本、数据迁移工具。

术语解释 —— 反射(Reflection):让程序"照镜子",从数据库读取已有表的结构信息(列、类型、约束),反向生成 Table 对象。

4. 继承映射 ​

一个 Python 类继承体系映射到一张表(单表继承)或多张表(联合表继承),支持多态查询:

python
from sqlalchemy import String, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column

class Person(Base):
    __tablename__ = "person"
    id: Mapped[int] = mapped_column(primary_key=True)
    type: Mapped[str] = mapped_column(String(20))
    __mapper_args__ = {"polymorphic_on": type, "polymorphic_identity": "person"}

class Employee(Person):
    __mapper_args__ = {"polymorphic_identity": "employee"}

特点:Person 和 Employee 存在同一张 person 表里,靠 type 列区分。查询 Person 时 SQLAlchemy 会根据 type 的值自动构造出正确的子类对象。这属于高级需求,初学阶段知道它的存在即可。

关键知识点 ​

方式适用场景
Declarative新项目首选,代码即文档
Imperative旧代码迁移、表结构动态生成
Reflection接手已有 MySQL 数据库、快速探索数据
继承映射有真实多态需求的领域模型

深入理解:该选哪种? ​

决策树很简单:

  1. 新项目 → Declarative(本教程所有章节的方式);
  2. 接手已有数据库,想快速看看里面有什么 → Reflection 探索,确认后再用 Declarative 正式建模;
  3. 维护祖传代码,表结构由别的系统管理 → Imperative 或 Reflection;
  4. 真有"动物→猫/狗"这种多态领域模型 → 继承映射。

进阶内容 ​

1. Reflection 的损耗。 反射 MySQL 数据库时,表上的索引、外键会被一并读取,但列注释、某些默认值、精度细节可能有损耗。反射出来的结构用于生产代码前,务必用 SHOW CREATE TABLE 对照核对。

2. 类型标注的隐藏福利。 Mapped[...] 不只是装饰:配合 mypy 或 IDE(如 PyCharm/VS Code),写 user.nam 时会提示"你是不是想写 name",赋值类型错了会画红线。养成标注习惯,Bug 在运行前就被拦截。

3. 混合使用的边界。 技术上可以一部分表用 Declarative、一部分用 Reflection(比如自己系统的表自己建模,对接第三方系统的表用反射),但要保证它们挂在同一个 MetaData 下,否则跨风格的 relationship 会找不到对方。

注意事项 ​

  • 一个项目坚持用一种映射风格,混用容易混乱。
  • Reflection 只反射"表",不会自动生成类;要获得类级别的 ORM 体验请用 Declarative。
  • 反射 MySQL 数据库时,表上的索引、外键会被一并读取,但注释、默认值等细节可能有损耗,生产使用前先核对。
  • 类型标注 Mapped[...] 让 IDE 能自动补全和检查,强烈建议养成习惯。

本章小结 ​

  • 四种映射方式:Declarative(主流)、Imperative(分离)、Reflection(逆向)、继承映射(多态);
  • 新项目用 Declarative,接手老库先用 Reflection 摸底;
  • Mapped[...] 类型标注是白送的 Bug 拦截器,务必使用。