SQLAlchemy 的两套 API

什么是"纯 SQLAlchemy Core 风格(非 ORM Session 查询)"

你贴的这段话是在说:这两个 Mapper 类没有用 SQLAlchemy 的 ORM 方式 (定义 class User(Base) 然后 session.query(User).filter(...)),而是用了 SQLAlchemy Core 的方式------直接拼 SQL 表达式、直接执行。

一、先分清 SQLAlchemy 的两套 API

SQLAlchemy 其实有两套独立但可混用的体系:

SQLAlchemy Core SQLAlchemy ORM
核心对象 Table、Column、select()、insert()、update()、delete() Base、class User(Base)、session.query()、session.add()
思维模式 面向 SQL:你写的是 SQL 表达式,Python 帮你拼成 SQL 面向对象:你操作的是 Python 对象,ORM 帮你翻译成 SQL
结果 返回 Row(类似 namedtuple) 返回对象实例(User 对象)
状态管理 无 session 状态跟踪 有 identity map、脏检查、级联、延迟加载
关系 手写 join relationship()、backref 自动处理
典型代码 conn.execute(select(user_table).where(user_table.c.id == 1)) session.query(User).filter(User.id == 1).first()

注意: Core 是 ORM 的底层 。ORM 内部就是靠 Core 把对象操作翻译成 SQL 的。所以"用 Core"不是"绕过 SQLAlchemy",而是用了更底层、更贴近 SQL 的那一层。

二、两种风格代码对比

假设有张表 bsv_meta:

ORM 风格(Session 查询)

python 复制代码
# 1. 定义模型
class BsvMeta(Base):
    __tablename__ = "bsv_meta"
    id = Column(Integer, primary_key=True)
    name = Column(String(64))
    enabled = Column(Boolean, default=False)

# 2. 查询
def get_meta(meta_id: int):
    return db.session.query(BsvMeta).filter(BsvMeta.id == meta_id).first()
    # 返回 BsvMeta 对象,可以 meta.name、meta.enabled 访问

# 3. 插入
def create_meta(name: str):
    m = BsvMeta(name=name)
    db.session.add(m)
    db.session.commit()
    return m

Core 风格(非 ORM Session 查询)

python 复制代码
# 1. 定义表(不是类!)
bsv_meta = Table(
    "bsv_meta", metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(64)),
    Column("enabled", Boolean, default=False),
)

# 2. 查询
def get_meta(meta_id: int):
    stmt = select(bsv_meta).where(bsv_meta.c.id == meta_id)
    row = db.session.execute(stmt).first()
    # 返回 Row,用 row.id / row.name 或 row._mapping["name"] 访问

# 3. 插入
def create_meta(name: str):
    stmt = insert(bsv_meta).values(name=name)
    result = db.session.execute(stmt)
    db.session.commit()
    return result.inserted_primary_key

关键区别:

  • Core 里表是 Table 对象,字段通过 bsv_meta.c.id(.c = columns)访问。
  • 查询用 select(...)、insert(...) 这种函数式构造 SQL 语句 ,再 execute()。
  • 返回的是 Row(像带字段名的元组),不是 ORM 对象。

三、"非 ORM Session 查询"到底强调什么

这句话里的"非 ORM Session 查询"是在强调:

它虽然用了 db.session.execute()(借用了 session 来执行),但执行的是 Core 的 select() 语句,而不是 ORM 的 session.query(Model)。

session 在这里只是**"执行器 + 事务上下文"**,不承担 ORM 的"对象状态跟踪"职责。也就是说:

python 复制代码
# ORM 查询:session 会跟踪返回的对象、缓存、脏检查
session.query(User).filter(...).all()

# Core 查询:session 只是执行 SQL,返回原始 Row,不跟踪对象
session.execute(select(user_table).where(...)).all()

同一个 session,两种用法。 第二种就是"非 ORM Session 查询"。

四、为什么 RuoYi-Python 的 Mapper 要用 Core 风格

结合你贴的那段描述(571 行的 CRUD + 互斥启用 + 引用计数 + 分页拦截),用 Core 有几个现实理由:

理由 说明
贴近若依框架原设计 RuoYi 的 Mapper 层本来就是"写 SQL / 映射 SQL"的思路(Java 版是 XML 写 SQL)。Core 风格最接近这种"我在写 SQL"的心智模型。
复杂 SQL 更好控制 互斥启用(UPDATE ... SET enabled=0 WHERE type=X)、引用计数(子查询 COUNT(*))、时间范围筛选,用 Core 拼比 ORM 的 relationship 更直观、更可控。
不想建 ORM 模型 元数据表往往字段多、结构固定、不需要对象关系,定义 6 个 ORM Model 反而累赘。直接 Table + select 更轻。
性能可控 Core 少一层 ORM 对象构造和状态跟踪,批量操作、复杂 join 时开销更小、行为更可预测。
分页拦截器 你提到的 flask.g.criterian_meta.page.stmt 注入若依分页------分页拦截器通常要拿到原始 select 语句 去改写成 COUNT 和 LIMIT。Core 的 select() 语句对象天生适合被拦截改写;ORM 的 session.query() 也能做,但 Core 更直接。

五、一句话总结

"纯 SQLAlchemy Core 风格(非 ORM Session 查询)"= 用 Table + select()/insert()/update()/delete() 来构造和执行 SQL,返回 Row;而不是用 class Xxx(Base) + session.query(Xxx) 返回 ORM 对象。 session 只当执行器和事务用,不参与对象状态管理。

六、对你的实际影响

如果你要读懂或修改 BsvMetaMapper / MeasureRecordMapper,记住:

  1. 里面出现的字段访问是 表.c.字段(如 bsv_meta.c.name),不是 对象.字段。
  2. 返回值是 Row,用 row.name 或 row._mapping["name"] 取值;不能 row.name = "x" 改完就自动同步(那是 ORM 才有)。
  3. 想看它到底执行了什么 SQL,可以 print(stmt.compile(compile_kwargs={"literal_binds": True}))。
  4. 分页不靠 LIMIT 手写,而是把 stmt 挂到 flask.g.criterian_meta.page.stmt,由若依的拦截器统一改写。

如果你想,可以把 BsvMetaMapper 里一个具体方法贴出来,我帮你逐行翻译成它实际执行的 SQL,你就能彻底对上号。

相关推荐
伞伞悦读1 小时前
【第45期】一行 eval 做计算器的代价:Python 输入校验和可测试的猜数字
python
用户8356290780511 小时前
Python 设置 Excel 单元格边框与样式
后端·python
10年前端老司机1 小时前
LLM降本提速三档对比:无缓存、普通缓存、语义缓存(LangChain生产落地)
人工智能·python·langchain
用户298698530141 小时前
Python 文档格式转换实战:RTF 转 PDF 与 HTML
python·html·api
天天被压力1 小时前
【Python 量化取数指南 #09】Python 拿港股通数据:港股通成交与港股财报实测
java·人工智能·python
杨杨杨大侠1 小时前
给 Agent 加一个“判断器”:聊聊 Laya、Jev,以及怎么部署和选择
人工智能·python·agent
SL_staff1 小时前
四维穿透式齐套判断:JVS-APS在制造计划系统中的技术实现
java·spring boot·python
夏天要喝冰可乐1 小时前
WorkBuddy 每天自动领积分!教你用云函数做个签到机器人
前端·python
小小张说故事1 小时前
XGBoost 入门指南:Python 梯度提升实战
python·机器学习