什么是"纯 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,记住:
- 里面出现的字段访问是
表.c.字段(如bsv_meta.c.name),不是对象.字段。 - 返回值是
Row,用row.name或row._mapping["name"]取值;不能row.name = "x"改完就自动同步(那是 ORM 才有)。 - 想看它到底执行了什么 SQL,可以
print(stmt.compile(compile_kwargs={"literal_binds": True}))。 - 分页不靠
LIMIT手写,而是把stmt挂到flask.g.criterian_meta.page.stmt,由若依的拦截器统一改写。
如果你想,可以把 BsvMetaMapper 里一个具体方法贴出来,我帮你逐行翻译成它实际执行的 SQL,你就能彻底对上号。