尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AI全栈实战 | 3.6-01 数据库与 ORM:学过 MyBatis 再看 SQLAlchemy,才知道“半自动“和“全自动“差在哪

AI全栈实战 | 3.6-01 数据库与 ORM:学过 MyBatis 再看 SQLAlchemy,才知道“半自动“和“全自动“差在哪 一个写过 MyBatis 的人第一次用 SQLAlchemy往往会冒出两个极端的念头要么觉得这不就是把 MyBatis 换个马甲要么在 Session 的坑里摔得怀疑人生——“怎么连个 sqlSession 都找不到”这篇我们用对照 MyBatis的方式拆解 Python 生态的数据访问层搞懂三件事Python 的 ORM 为什么敢叫全自动它的抽象在什么时候会漏以及——一个连 SQL 都替你生成的全自动 ORM到底把控制权交给了谁一、从 MyBatis 到 SQLAlchemy两种哲学的对撞先摆一张对照表这是全文的骨架维度MyBatisJava 半自动SQLAlchemy / Django ORMPython 全自动SQL 谁写你写XML/注解里手写框架生成你用 Python 对象表达意图映射谁管手写 resultMap / 注解声明式模型类自动映射灵活性极高SQL 尽在掌握中高复杂 SQL 可用原生语句兜底学习曲线中间件思维上手快概念多Session/事务/声明式擅长复杂查询、DBA 主导、需要精细控制CRUD、快速开发、跟随模型演进一句话理解差异MyBatis 是你在 SQL 世界的代表人——SQL 始终由你书写框架只负责在 Java 对象和结果集之间做搬运SQLAlchemy 则是替你写 SQL 的翻译官——你用 Python 对象描述要什么它负责翻译成 SQL 并执行。MyBatis 把手SQL留给你把腿映射拿走SQLAlchemy 把手也拿走了——这就是半自动和全自动的根本分野。二、选库先行SQLite / MySQL / PostgreSQL别纠结太久数据访问层第一步不是写代码是选库。很多人在这上面反复横跳浪费时间。给你一个务实判断数据库定位何时选它SQLite单文件嵌入式本地开发、个人项目、原型验证、工具脚本。零配置MySQL最流行关系库生产主力、团队项目、中小规模、生态资料最全PostgreSQL功能最全的关系库需要高级特性JSON/全文检索/地理/GIS、数据严谨场景开发阶段建议先用 SQLite 快速把逻辑跑通再用一个环境变量切换生产库——ORM 的价值之一就是帮你屏蔽底层方言差异让你前期不必锁死数据库。三、SQLAlchemy 的两层 APICore 和 ORM先搞懂为什么分两层SQLAlchemy 的设计常被忽略却极其重要它提供两层 API。┌─────────────────────────────────────────────┐ │ 应用层代码 │ ├─────────────────────────────────────────────┤ │ ORM高层Model 类 ↔ 表 │ ← 大多数人只在这层 │ Session 管理对象生命周期 │ ├─────────────────────────────────────────────┤ │ Core中层Table SQL Expression │ ← 需要精细控制时下来 │ Engine / Connection 直接执行 │ ├─────────────────────────────────────────────┤ │ DBAPI 方言psycopg2 / pymysql ... │ ← 最底层 └─────────────────────────────────────────────┘为什么分两层因为对象关系映射这个抽象在复杂查询时会漏下面第五节细说。当 ORM 的表达力不够时你可以降级到 Core 层直接写 SQL Expression甚至用原生 SQL——不需要换框架。这就是 SQLAlchemy 比很多纯 ORM更耐用的原因它给你留了一条下探的梯子。先看最标准的声明式模型ORM 层fromsqlalchemyimportcreate_engine,Column,Integer,String,ForeignKeyfromsqlalchemy.ormimportdeclarative_base,relationship,sessionmaker enginecreate_engine(sqlite:///blog.db,echoFalse)# 生产换 mysqlpymysql://...Basedeclarative_base()classAuthor(Base):__tablename__authorsidColumn(Integer,primary_keyTrue)nameColumn(String(50),nullableFalse)postsrelationship(Post,back_populatesauthor)classPost(Base):__tablename__postsidColumn(Integer,primary_keyTrue)titleColumn(String(200),nullableFalse)author_idColumn(Integer,ForeignKey(authors.id))authorrelationship(Author,back_populatesposts)Base.metadata.create_all(engine)# 自动建表Sessionsessionmaker(bindengine)这段声明式代码本身就在回答一个问题为什么 MyBatis 需要 resultMap而这里不需要因为这里把表结构直接写成了 Python 类字段即列、关系即relationship——映射信息是声明出来的框架读类就知道如何映射不需要一份单独的映射配置。四、SessionPython ORM 最容易被坑的一环用过 MyBatis 的人会自然找sqlSession但 SQLAlchemy 的Session是另一个物种——它不是一次请求一个连接而是一个工作单元Unit of Work的缓存层。这是新手最容易翻车的地方。典型坑Session 的缓存让你以为写进去了其实没有。# 错误的直觉new_author 明明对象上看到了 idsessionSession()new_authorAuthor(name张三)session.add(new_author)# 没有 commit()对象可能还没真正入库id 可能是临时的# 正确做法事务要么提交要么回滚sessionSession()try:authorAuthor(name张三)session.add(author)session.commit()# 真正 flush 到数据库事务提交print(author.id)# 现在 id 才是数据库里的真实主键exceptException:session.rollback()# 出错回滚别让脏数据留在事务里finally:session.close()Session 的正确心智它管理一个待提交的变更集合。add()只是把对象放进集合commit()才真正写库。忘 commit 是 Python ORM 的第一大事故比 MyBatis 忘session.commit()更容易发生因为很多人根本没意识到有个隐式事务在工作。再提醒一个生命周期坑不要把 Session 设为全局单例在多个线程共享——Session不是线程安全的。正确做法是每请求/每工作单元一个 Session配合依赖注入或 contextvar用完即关。五、N1 查询全自动 ORM 的隐藏账单这是全自动最隐蔽的代价。看这段自然的代码sessionSession()authorssession.query(Author).all()# 1 条 SQL 查所有作者forauthorinauthors:# ⚠️ 下面循环里每条都触发查询print(author.name,len(author.posts))# N 条 SQL 查每个作者的 posts# 总共 1 N 条 SQL —— N1 问题ORM 替你懒加载了author.posts——当你真正访问它时才发查询。这个便利在循环里就成了性能黑洞查 100 个作者就发 101 条 SQL。解法一Django 风格一次性预加载关联MyBatis 也有类似的collection联表或两步查。fromsqlalchemy.ormimportjoinedload authorssession.query(Author).options(joinedload(Author.posts)# 用 JOIN 一次性把 posts 一起查出来).all()解法二显式用 Core/原生 SQL 联表绕过 ORM 的懒加载。关键认知ORM 的懒加载在单个对象取关联时很方便但在循环里批量取关联时是灾难。这个反模式在 MyBatis、Hibernate、SQLAlchemy、Django ORM 里全都会发生——因为它们是同一个问题抽象层替你做的聪明事在你没意识到时变成隐藏账单。定位手段给引擎开 SQL 日志echoTrue或用 logging 打印 SQL一旦发现循环里 SQL 数量暴涨基本就是 N1。六、Django ORM 对比链式查询与 select_related如果你走 Django 路线它的 ORM 是另一套风格——链式 QuerySet# Django ORM链式、惰性求值fromdjango.contrib.auth.modelsimportUser active_admins(User.objects.filter(is_activeTrue,is_staffTrue)# 条件.select_related(profile)# 外键预加载对应 joinedload.order_by(-date_joined)# 排序[:10]# 切片 LIMIT)# 惰性上面只是构建 QuerySet真正取值时才发 SQLforuserinactive_admins:print(user.username,user.profile.bio)对应关系记忆Django 的select_related外键JOIN 预取≈ SQLAlchemy 的joinedloadDjango 的prefetch_related多对多/反向二次查询≈ SQLAlchemy 的selectinload两者都在解决同一个 N1 问题只是 API 名字不同。Django ORM 与 SQLAlchemy 的取舍Django ORM 和 Django 全家桶深度绑定、语法糖多、上手快适合跟着 Django 一条龙SQLAlchemy 更独立、更贴近 SQL、两层 API 更灵活适合 FastAPI/Flask 的自由组装路线。没有谁绝对好取决于你的框架选型。小结MyBatis 老手眼中的 Python ORM 速查我的 MyBatis 经验对应到 Python ORMsqlSession commitSession commit注意隐式事务resultMap手写映射声明式 Model 类自动映射collection/ 两步查joinedload/select_relatedSQL 尽在掌握Core 层 / 原生 SQL 兜底Mapper 接口 XMLModel 类 Query / QuerySet数据库选型SQLite(dev) / MySQL / PostgreSQL核心洞察收个尾MyBatis 的半自动把 SQL 控制权留给你代价是你得手写映射Python ORM 的全自动替你写 SQL代价是你得理解 Session/懒加载这些隐式机制否则会踩 N1、忘 commit 的坑。抽象泄漏定律在这里的体现是越自动的层越需要你懂它漏出来的那部分——这就是为什么高手既会用 ORM又能随时下探到 SQL。
返回列表