Mapped Class
Mapped Class即映射类,他是 ORM 的核心。在 SQLAlchemy 中提供了两种形式的映射类:
- 声明式映射(Declarative Mapping): 目前推荐的方式,类似 Python 常规的类定义
- 命令式映射(Imperative Mapping): 通过命令来包装空类,他主要用于对现有的代码进行包装(内省也通过这种方式来进行类映射)
registry
registry是映射类的通用注册表,他提供了映射类的配置钩子。registry 类似 SQLALchemy Core
中的MetaData来表示描述数据库,实际上创建 registry 对象的第一个参数就是 MetaData:
就像 Core 中所有的 Table 都应该注册到 MetaData 一样,所有的映射类(无论是声明式映射还是命令式映射)也都应该被注册到 registry 对象中,这是通过 registry 对象上绑定的三个方法来实现的:
registry.generate_base()
: 返回一个声明式映射基类(DeclarativeBase),所有基于该基类创建的类都会被注册到 registry 中registry.mapped()
: 类装饰器,他类似声明式映射基类,只不过使用装饰器的形式来注册registry.map_imperatively(class, Table)
: 使用 class(空类)对 Table 进行包装,这个是命令式映射的核心方法
由于 MetaData 也包含在 registry 中,因此创建绑定到 registry 中表的方式:
Python
# 全部创建
Base.metadata.create_all(engine)
# 单表创建
Base.metadata.tables['user'].create(engine)
# 默认情况如果存在创建会失败,可以指定 checkfirst = True 来在创建前进行检查
Base.metadata.tables[User.__table__].create(engine, checkfirst=True)
声明式映射
声明式映射是现代 SQLAlchemy 最典型的映射方式,他通过构造继承类来实现,具体分为两个步骤:
- 创建声明基类(DeclarativeBase): 他可以看作是 registry 的类表示形式,使用
registry.generate_base()
是最方便的创建声明基类的方式 - 继承声明基类来构造映射类: 他对应了表,其中的属性表示表中的字段
构造声明基类
所谓的声明基类实际上就是用类的方式来表示 registry,他表示一个数据库对象,所有需要注册到数据库的表都应该是这个基类的子类,通过这种继承的关系来自动注册他:
Python
from sqlalchemy.orm import DeclarativeBase
# 使用默认的 DeclarativeBase 超类来构造声明基类
# 这里的 registry 使用默认的 registry 来创建
class Base(DeclarativeBase):
pass
# 使用现有的 registry 可以
class Base(DeclarativeBase):
registry = my_own_registry
# 另一种构造 Base 的方式,他与上面的等价的
Base = my_own_registry.generate_base()
# 最终的子类定义
class User(Base):
pass
# 装饰器模式同样可以,他替代了继承的定义形式
@my_own_registry.mapped
class User:
pass
映射类
通过继承声明基类来构建映射类:
Python
from datetime import datetime
from typing import Optional
from sqlalchemy import ForeignKey,func,Integer,String
from sqlalchemy.orm import DeclarativeBase,Mapped,mapped_column,relationship
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "user"
id = mapped_column(Integer, primary_key=True)
name: Mapped[str]
fullname: Mapped[Optional[str]]
nickname: Mapped[Optional[str]] = mapped_column(String(64))
create_date: Mapped[datetime] = mapped_column(insert_default=func.now())
addresses: Mapped[List["Address"]] = relationship(back_populates="user")
class Address(Base):
__tablename__ = "address"
id = mapped_column(Integer, primary_key=True)
user_id = mapped_column(ForeignKey("user.id"))
email_address: Mapped[str]
user: Mapped["User"] = relationship(back_populates="addresses")
他实际上等效于 Table:
Python
user_table = Table(
"user", # 转义成 User.__tablename__ 属性表示表名
Base.metadata, # 实际上就是 Base,被定义在 registry 中
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("fullname", String()),
Column("nickname", String(30)),
)
# 还有一种混合声明方式
class User(Base):
__table__ = Table(
"user",
Base.metadata,
Column("id", Integer, primary_key=True),
Column("name", String),
Column("fullname", String),
Column("nickname", String),
)
# 甚至一个 SELECT(即子表) 也可以用这种方式来构建映射类
from sqlalchemy import func, select
customer_select = (
select(customers)
.where(id>10)
)
class Customer(Base):
__table__ = customer_select
# 不过这主要在反射表时使用
engine = create_engine("postgresql+psycopg2://user:pass@hostname/my_existing_database")
class Base(DeclarativeBase):
pass
class MyClass(Base):
__table__ = Table(
"mytable",
Base.metadata,
autoload_with=engine,
)
mapped_column: 包装列
映射类的核心在于定义列,这使用 mapped_column()
方法来实现,该方法接受与 Column 相同的属性,他可以看作是 Column 的包装:
Python
mapped_column(
__name: str, # 字段名称, 他与类字段名,类字段名在代码中使用,这个就是数据库的字段名如果两者相同
__type: TypeEngine, # 数据类型
nullable=True, # nullable 约束
primary_key=False, # 主键约束
...
)
为了能够提供类型提示的功能,还提供了 Mapped[type]
来实现,因此一个完整的现代化列定义:
Python
# Integer 主键
id:Mapped[int] = mapped_column(Integer, primary_key=True)
# String 类型,相当于自动添加了 mapped_column(String, nullable=False)
name: Mapped[str]
# 这个相当于 mapped_column(String)
fullname: Mapped[Optional[str]]
nickname: Mapped[Optional[str]] = mapped_column(String(64))
命令式映射
命令式映射则依赖 Table 对象,之后使用 registry.map_imperatively()
方法进行包装来实现:
Python
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import registry
mapper_registry = registry()
user_table = Table(
"user",
mapper_registry.metadata,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("fullname", String(50)),
Column("nickname", String(12)),
)
class User:
pass
mapper_registry.map_imperatively(User, user_table)
他的好处明显就是能够充分利用已有的 Table 对象,但是问题也比较明显就是非常不直观并且无法提供类型检查等功能。