Appearance
ORM 与迁移
上一篇的 SQL 全是裸字符串——字段名改了要满文件找,查询条件复杂了 SQL 又长又难读,换数据库方言还得重写。ORM 用 Python 类描述表结构、用 Python 方法做查询,把这些问题收走了。
Python 最主流的 ORM 是 SQLAlchemy。配合 Alembic 做数据库迁移——表结构变了(加字段、改类型),用迁移脚本管理变更,不用手动 ALTER TABLE。
一、SQLAlchemy 模型
python
from sqlalchemy import String, Text
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from datetime import datetime
class Base(DeclarativeBase):
pass
class Article(Base):
__tablename__ = "articles" # 对应数据库里的表名
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
title: Mapped[str] = mapped_column(String(200))
content: Mapped[str] = mapped_column(Text)
status: Mapped[str] = mapped_column(String(20), default="draft")
created_at: Mapped[datetime] = mapped_column(default=datetime.now)Article 类就是数据库表的描述——每个属性对应一列。Mapped[int] 标注类型,mapped_column 配置列的属性(主键、自增、长度、默认值)。这个类就是上一篇 CREATE TABLE 那段 SQL 的等价物,但它是 Python 代码,IDE 有类型提示,改字段名时能全局搜索。
二、创建引擎和会话
数据库连接通过"引擎"管理。引擎管连接池,会话(Session)管一次数据库操作的上下文:
python
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# 连接 SQLite(换成 MySQL 只改连接字符串)
engine = create_engine("sqlite:///articles.db", echo=False)
SessionLocal = sessionmaker(bind=engine)
# 根据模型创建所有表(开发时用,生产用迁移)
Base.metadata.create_all(engine)create_engine 的连接字符串决定连什么数据库:
- SQLite:
sqlite:///articles.db - MySQL:
mysql+pymysql://user:password@localhost:3306/dbname - PostgreSQL:
postgresql://user:password@localhost:5432/dbname
echo=True 会把生成的 SQL 打到控制台——学 ORM 时开一下,能看到每个操作背后生成了什么 SQL,对照着理解很有帮助。
三、CRUD 操作
python
from sqlalchemy.orm import Session
session = SessionLocal()1 新增
python
new_article = Article(title="Python 入门", content="基础语法...", status="published")
session.add(new_article)
session.commit()
print(new_article.id) # commit 后 id 自动填充session.add() 把对象加入会话,commit() 时生成并执行 INSERT SQL。commit 之后 new_article.id 才有值——自增 ID 是数据库分配的。
2 查询
python
# 查全部
articles = session.query(Article).all()
# 条件查询
published = session.query(Article).filter(Article.status == "published").all()
# 查单条
article = session.query(Article).filter(Article.id == 1).first()filter(Article.status == "published") 是 SQLAlchemy 的查询语法——Article.status == "published" 在普通 Python 里是比较运算返回 True/False,但在 SQLAlchemy 里它返回一个查询条件对象,被翻译成 WHERE status = 'published'。
3 更新
python
article = session.query(Article).filter(Article.id == 1).first()
article.status = "published"
session.commit()直接改对象的属性,commit() 时 SQLAlchemy 自动生成 UPDATE SQL。比手写 UPDATE articles SET status = ? WHERE id = ? 简洁得多。
4 删除
python
article = session.query(Article).filter(Article.id == 1).first()
if article:
session.delete(article)
session.commit()5 用完后关闭会话
python
session.close()四、在 FastAPI 里用会话
跟上一篇的 SQLite 依赖注入一样,用 Depends 管理会话生命周期:
python
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
app = FastAPI()
def get_db():
session = SessionLocal()
try:
yield session
finally:
session.close()
@app.get("/articles")
def list_articles(db: Session = Depends(get_db)):
articles = db.query(Article).order_by(Article.id.desc()).all()
return [{"id": a.id, "title": a.title, "status": a.status} for a in articles]
@app.post("/articles")
def create_article(title: str, content: str, db: Session = Depends(get_db)):
article = Article(title=title, content=content)
db.add(article)
db.commit()
db.refresh(article) # 刷新,拿到数据库分配的 id
return {"id": article.id, "title": article.title}db.refresh(article) 在 commit 后重新查一次,拿到数据库分配的 id 和默认值。
五、Alembic 迁移
Base.metadata.create_all() 只能建表,不能改已有的表结构。线上数据库已经有数据,直接删表重建会丢数据。加字段、改类型这些表结构变更,用 Alembic 管理。
bash
uv add alembic初始化:
bash
uv run alembic init alembic生成的目录结构:
text
alembic/
├── versions/ # 迁移脚本存这里
├── env.py # 迁移环境配置
└── script.py.mako # 迁移脚本模板
alembic.ini # Alembic 配置文件配置 alembic/env.py,让它知道模型在哪:
python
# alembic/env.py 里,添加模型导入
from main import Base ① 你的模型定义文件
target_metadata = Base.metadata配置数据库连接(alembic.ini):
ini
sqlalchemy.url = sqlite:///articles.db1 生成迁移脚本
模型加了新字段(比如 summary)后,自动生成迁移脚本:
bash
uv run alembic revision --autogenerate -m "add summary to articles"Alembic 对比模型和当前数据库的差异,生成一个迁移脚本(versions/xxx_add_summary_to_articles.py):
python
def upgrade():
op.add_column("articles", sa.Column("summary", sa.Text(), nullable=True))
def downgrade():
op.drop_column("articles", "summary")upgrade 是正向迁移(加字段),downgrade 是回退(删字段)。autogenerate 生成的不一定完全对,复杂变更要人工检查和修改脚本。
2 执行迁移
bash
uv run alembic upgrade head # 执行所有待执行的迁移
uv run alembic current # 看当前迁移到哪个版本
uv run alembic downgrade -1 # 回退一个版本head 表示最新的迁移版本。生产部署时先 alembic upgrade head 再启动应用——这样表结构总是跟代码一致。