6.2 依赖注入:管理数据库会话
这是 FastAPI × SQLAlchemy 集成的核心一节。目标:让每个请求自动获得一个 Session、请求结束自动关闭——靠 FastAPI 的杀手锏"依赖注入"实现。
一、问题:每个接口都要 Session,谁来管?
按第 2 章的知识,正确的 Session 用法是"每个工作单元一个,用完就关"。对 Web 应用来说:
一个 HTTP 请求 = 一个工作单元 = 一个 Session。
朴素写法是在每个接口函数里手动开关:
@app.get("/users")
def list_users():
with SessionLocal() as session: # 每个接口都写一遍
return session.scalars(select(User)).all()
能跑,但 50 个接口就要写 50 遍 with,而且事务提交/回滚逻辑散落各处。FastAPI 给了更优雅的方案。
二、依赖注入是什么?
依赖注入(Dependency Injection):接口函数在参数里声明"我需要一个 XX",框架负责制造并递给你,用完还负责清理。
def get_db(): # 这是一个"依赖":负责生产 session
...yield session...
@app.get("/users")
def list_users(db: Session = Depends(get_db)): # 声明:我需要一个 session
...直接用 db... # FastAPI 造好递进来
好处:资源的"创建-使用-销毁"彻底分离,接口函数只管业务。
三、标准配置:database.py
正式项目把数据库相关的底座集中到一个文件。这是你以后每个 FastAPI 项目都会写的"标准模板":
# database.py
from typing import Generator
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
# SQLite 专属参数:check_same_thread=False
# (FastAPI 可能在不同线程使用同一连接,SQLite 默认禁止,需关闭该检查)
engine = create_engine(
"sqlite:///app.db",
connect_args={"check_same_thread": False},
echo=True, # 学习期间开着,上线关掉
)
SessionLocal = sessionmaker(bind=engine, autoflush=False)
class Base(DeclarativeBase):
pass
def get_db() -> Generator[Session, None, None]:
"""依赖函数:每个请求一个 session,用完自动关闭"""
with SessionLocal() as session:
yield session
get_db 的执行时序(理解 yield 依赖)
请求到达
→ FastAPI 调用 get_db(),执行到 yield,把 session 递给接口函数
→ 接口函数用 session 干活、返回结果
→ 响应发出后,回到 yield 之后 → with 退出 → session.close()
yield 前是"准备资源",yield 后是"清理资源"——像三明治一样把你的接口逻辑夹在中间。即使接口抛异常,清理也一定执行(with 保证)。
想要"自动提交/自动回滚"?升级版:
def get_db() -> Generator[Session, None, None]:
with SessionLocal() as session:
try:
yield session
session.commit() # 接口正常结束 → 提交
except Exception:
session.rollback() # 接口抛了异常 → 回滚
raise
这实现了 5.2 节的原则——"一个请求一个事务,谁代表完整业务谁 commit"。本教程为了教学直观,接口里仍会显式写 commit;两种风格都常见,团队统一即可。
四、在接口中使用
# main.py
from fastapi import Depends, FastAPI
from sqlalchemy import select
from sqlalchemy.orm import Session
from database import Base, engine, get_db
from models import User # 你的 SQLAlchemy 模型
Base.metadata.create_all(bind=engine) # 学习期直接建表(正式项目用 Alembic)
app = FastAPI()
@app.get("/users")
def list_users(db: Session = Depends(get_db)):
users = db.scalars(select(User)).all()
return [{"id": u.id, "name": u.name} for u in users]
@app.post("/users", status_code=201)
def create_user(name: str, email: str, db: Session = Depends(get_db)):
user = User(name=name, email=email)
db.add(user)
db.commit()
return {"id": user.id}
db: Session = Depends(get_db) 逐词解读:
db—— 参数名,随意起: Session—— 类型注解,让 IDE 知道它是 SQLAlchemy 会话(自动补全.scalars等)= Depends(get_db)—— 告诉 FastAPI:"这个参数不是客户端传的,调get_db生成"
五、更现代的写法:Annotated 类型别名
Depends(get_db) 每个接口都写一遍还是重复。用 Annotated 定义一次别名,处处使用:
# database.py 末尾追加
from typing import Annotated
from fastapi import Depends
DbSession = Annotated[Session, Depends(get_db)]
# 接口签名瞬间清爽:
@app.get("/users")
def list_users(db: DbSession):
return db.scalars(select(User)).all()
@app.get("/users/{user_id}")
def get_user(user_id: int, db: DbSession):
...
这是 FastAPI 官方当前推荐的风格,第 7 章实战全程使用。
六、应用生命周期:lifespan
建表、预热连接等"启动时做一次"的事,规范做法是 lifespan(应用级的 yield 依赖):
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
Base.metadata.create_all(bind=engine) # 启动时:建表
yield # ← 应用运行期间
engine.dispose() # 关闭时:释放连接池
app = FastAPI(lifespan=lifespan)
七、依赖注入的隐藏福利:可测试性
测试时想用内存数据库而不碰真实库?覆盖依赖即可,业务代码零改动:
# test 用的 session 工厂(内存 SQLite)
test_engine = create_engine("sqlite://", connect_args={"check_same_thread": False},
poolclass=StaticPool)
TestSession = sessionmaker(bind=test_engine)
def override_get_db():
with TestSession() as session:
yield session
app.dependency_overrides[get_db] = override_get_db # 一行替换!
第 7.3 节写接口测试时会真正用上它。
📝 本节小结
- 原则:一个请求 = 一个 Session,由依赖函数
get_db统一生产和回收 get_db模板:with SessionLocal() as session: yield session——yield 前准备、后清理- 接口声明
db: Session = Depends(get_db),或用Annotated别名db: DbSession - SQLite + FastAPI 记得
connect_args={"check_same_thread": False} - 启动逻辑放 lifespan;测试用
dependency_overrides换库
下一节解决"两种模型"的配合问题 → 6.3 Pydantic 与 ORM 模型的配合