Skip to content

第 05 章 数据模型 Model ​

学习目标 ​

学完本章,你应该能:

  1. 理解 SQLAlchemy 2.0 的现代写法(Mapped / mapped_column)
  2. 掌握 fba 中模型基类(Base)和 Mixin 的用法
  3. 能自己定义一个数据库模型

一、Model 是什么 ​

Model(模型) 是数据库表的 Python 映射。通过 SQLAlchemy 这个 ORM 工具,你可以用 Python 类来表示一张数据库表:

  • 一个类 = 一张表
  • 类的属性 = 表的字段(列)

ORM:对象关系映射。让你用"操作 Python 对象"的方式操作数据库,底层自动翻译成 SQL,不必手写 SQL 语句。

类 ↔ 表的映射关系 ​

二、SQLAlchemy 2.0 现代语法 ​

fba 使用 SQLAlchemy 2.0 的最新写法,核心是两个关键词:

  • Mapped[...]:声明某个属性的类型(用于类型标注)
  • mapped_column(...):声明这个属性对应数据库的一列,并设置列属性

基本示例 ​

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

class User(Base):
    __tablename__ = 'sys_user'

    username: Mapped[str] = mapped_column(String(32), comment='用户名')
    age: Mapped[int | None] = mapped_column(comment='年龄')

解释:

  • Mapped[str]:告诉类型检查器,username 是一个字符串
  • mapped_column(String(32)):对应数据库里长度 32 的字符串列
  • int | None:表示可空(允许 NULL)

三、通用模型基类 ​

fba 的通用模型定义在 backend/common/model.py,这是理解 fba Model 的关键。

1. 主键(id_key) ​

fba 不提供自动主键,需要手动声明主键。它提供了一个 id_key 类型别名:

python
id_key = Annotated[
    int,
    mapped_column(
        BigInteger,
        primary_key=True,     # 主键
        unique=True,          # 唯一
        index=True,           # 索引
        autoincrement=True,   # 自增
        comment='主键 ID',
    ),
]

使用时直接 id: Mapped[id_key] 即可。

Annotated:Python 类型标注的"别名",把类型 + 额外信息打包成一个可复用的名字。

2. Mixin 类(混入) ​

Mixin 是一种"把公共字段打包,让多个模型复用"的技术。fba 提供了几个常用 Mixin:

日期时间 Mixin(已集成在 Base 中):

python
class DateTimeMixin(MappedAsDataclass):
    created_time: Mapped[datetime] = mapped_column(TimeZone, default_factory=timezone.now, comment='创建时间')
    updated_time: Mapped[datetime | None] = mapped_column(TimeZone, onupdate=timezone.now, comment='更新时间')

逻辑删除 Mixin(已集成在 Base 中):

python
class LogicalDeleteMixin(MappedAsDataclass):
    deleted: Mapped[int] = mapped_column(default=0, comment='是否已删除(0:否;id:是)')
    deleted_time: Mapped[datetime | None] = mapped_column(comment='删除时间')

逻辑删除:不是真的删掉数据,而是打个标记(deleted=1),查询时自动过滤。好处是数据可恢复、可追溯。

3. 两个核心基类 ​

python
# 数据类基类:带数据类集成,但不含日期时间和逻辑删除
class DataClassBase(MappedAsDataclass, MappedBase):
    __abstract__ = True

# Base 基类:数据类 + 日期时间 + 逻辑删除,最常用
class Base(DataClassBase, DateTimeMixin, LogicalDeleteMixin):
    __abstract__ = True

__abstract__ = True:表示这是"抽象基类",只用来继承,不会真的建一张表。

大部分业务模型直接继承 Base 即可,它会自动带上主键、创建时间、更新时间、逻辑删除标记。

基类继承关系图 ​

四、主键模式:自增 ID vs 雪花 ID ​

fba 支持两种主键方案:

模式特点适用场景
自增 ID数据库自动递增(1,2,3...)单机、简单项目
雪花 ID全局唯一的长整数 ID分布式、多实例场景

默认是自增 ID。切换雪花 ID 需要修改配置 DATABASE_PK_MODE,详见官方文档的"主键"章节。

雪花 ID(Snowflake):一种分布式全局唯一 ID 生成算法,生成的 ID 是很大的数字,能保证多台机器同时生成也不重复。

五、字符串类型规范 ​

fba 对字符串长度有约定俗成的规范:

  • 长文本:使用 fba 内置的 UniversalText(兼容 PostgreSQL 和 MySQL)
  • 常见文本:通常用 32、64、128、256、512 这几个长度单位

例如用户名用 String(32),描述用 String(256),正文用 UniversalText。

六、完整示例:定义一个用户模型 ​

综合以上知识,定义一个业务模型大概长这样:

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

from backend.common.model import Base, id_key


class User(Base):
    """用户表"""

    __tablename__ = 'sys_user'

    id: Mapped[id_key] = mapped_column(init=False)  # 主键
    username: Mapped[str] = mapped_column(String(32), unique=True, comment='用户名')
    nickname: Mapped[str | None] = mapped_column(String(64), comment='昵称')
    email: Mapped[str | None] = mapped_column(String(128), comment='邮箱')

关键点:

  • 继承 Base,自动获得 id、created_time、updated_time、deleted 等字段
  • __tablename__ 指定表名
  • 每个字段都写 comment,方便生成文档

七、常见疑问 ​

Q1:为什么 fba 不提供自动主键? 为了灵活性——你可以自由选择自增 ID 或雪花 ID,且主键定义清晰可见,避免"黑盒"。

Q2:mapped_column(init=False) 是什么意思? 因为继承的是数据类基类(MappedAsDataclass),默认字段会出现在 __init__ 参数里。init=False 表示主键不在构造函数里显式传参(由数据库/算法生成)。

Q3:逻辑删除会影响查询吗? 会。查询时需要自动过滤 deleted=0 的记录,删除操作变成"打标记"而非真删。这一点在 CRUD 层需要配合处理。

本章小结 ​

关键要点回顾:

  • Model 用 SQLAlchemy 2.0 的 Mapped + mapped_column 定义
  • fba 通用模型在 backend/common/model.py
  • 主键手动声明,用 id_key 别名
  • 继承 Base 自动获得时间戳 + 逻辑删除字段
  • 字符串长度规范:32/64/128/256/512,长文本用 UniversalText

下一步: 模型定义好了,第 06 章我们学习如何用 Schema 校验进出接口的数据。


对应官方文档:模型(backend/reference/model.html)