异步编程与数据库集成(SQLModel + SQLAlchemy)
💡 本部分目标:掌握在 FastAPI 中使用异步方式连接数据库;学会使用 SQLModel(由 FastAPI 作者开发)定义模型、操作数据,构建真实可运行的博客系统。
✅ 一、为什么需要数据库?
前几部分我们使用"模拟数据",但真实应用必须持久化数据。FastAPI 官方推荐使用:
- SQLModel:结合 Pydantic(用于 API)和 SQLAlchemy(用于数据库)的库
- 支持 SQLite(开发)、PostgreSQL、MySQL 等
- 代码简洁,类型安全,与 FastAPI 无缝集成
✅ 二、安装依赖
在你的虚拟环境中运行:
bash
pip install sqlmodel
💡 SQLModel 内部已包含 SQLAlchemy 和 Pydantic,无需额外安装。
✅ 三、理解异步(async/await)基础
FastAPI 原生支持异步,但 SQLModel 默认是同步的 。对于初学者,先用同步方式学习数据库操作更简单 ,后续可升级到异步(如使用 asyncpg + SQLAlchemy Core)。
📌 本教程采用 同步 SQLModel(适合入门),但会说明如何过渡到异步。
✅ 四、步骤1:定义数据库模型
使用 SQLModel,一个类既是 Pydantic 模型 (用于 API),又是 SQLAlchemy 表(用于数据库)。
示例:博客文章模型
python
# models.py
from sqlmodel import SQLModel, Field
from typing import Optional
class Post(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
title: str
content: str
published: bool = False # 默认草稿
🔍 说明:
table=True:表示这是一个数据库表id是主键,自增(SQLite 自动处理)Optional[int]表示创建时可不提供 id(由数据库生成)
✅ 五、步骤2:创建数据库和表
在 main.py 中初始化数据库。
python
# main.py
from fastapi import FastAPI
from sqlmodel import create_engine, Session, SQLModel
from models import Post
# 使用 SQLite(文件数据库,适合开发)
DATABASE_URL = "sqlite:///./blog.db"
# echo=True 会打印 SQL 语句(调试用)
engine = create_engine(DATABASE_URL, echo=True)
# 创建所有表
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
app = FastAPI()
@app.on_event("startup")
def on_startup():
create_db_and_tables()
⚠️ 注意:SQLite 在 Windows 上需加
?check_same_thread=False:
pythonengine = create_engine("sqlite:///./blog.db?check_same_thread=False")
✅ 六、步骤3:实现 CRUD 接口
6.1 创建文章(Create)
python
from fastapi import FastAPI, Depends, HTTPException
from sqlmodel import Session, select
# 依赖:提供数据库会话
def get_session():
with Session(engine) as session:
yield session
@app.post("/posts/", response_model=Post, status_code=201)
def create_post(post: Post, session: Session = Depends(get_session)):
session.add(post)
session.commit()
session.refresh(post) # 获取数据库生成的 id
return post
6.2 查询所有文章(Read)
python
@app.get("/posts/", response_model=list[Post])
def read_posts(session: Session = Depends(get_session)):
posts = session.exec(select(Post)).all()
return posts
6.3 查询单篇文章
python
@app.get("/posts/{post_id}", response_model=Post)
def read_post(post_id: int, session: Session = Depends(get_session)):
post = session.get(Post, post_id)
if not post:
raise HTTPException(status_code=404, detail="文章未找到")
return post
6.4 更新文章(Update)
python
@app.put("/posts/{post_id}", response_model=Post)
def update_post(post_id: int, post_update: Post, session: Session = Depends(get_session)):
post = session.get(Post, post_id)
if not post:
raise HTTPException(status_code=404, detail="文章未找到")
# 更新字段
post_data = post_update.dict(exclude_unset=True)
for key, value in post_data.items():
setattr(post, key, value)
session.add(post)
session.commit()
session.refresh(post)
return post
6.5 删除文章(Delete)
python
@app.delete("/posts/{post_id}", status_code=204)
def delete_post(post_id: int, session: Session = Depends(get_session)):
post = session.get(Post, post_id)
if not post:
raise HTTPException(status_code=404, detail="文章未找到")
session.delete(post)
session.commit()
return # 204 无响应体
✅ 七、完整项目结构
blog-api/
├── main.py
├── models.py
└── blog.db ← 自动生成
models.py(完整)
python
from sqlmodel import SQLModel, Field
from typing import Optional
class Post(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
title: str
content: str
published: bool = False
main.py(完整)
python
from fastapi import FastAPI, Depends, HTTPException
from sqlmodel import create_engine, Session, SQLModel, select
from models import Post
from typing import List
# 数据库设置
DATABASE_URL = "sqlite:///./blog.db"
engine = create_engine(DATABASE_URL, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def get_session():
with Session(engine) as session:
yield session
app = FastAPI(title="博客 API")
@app.on_event("startup")
def on_startup():
create_db_and_tables()
# CRUD 接口
@app.post("/posts/", response_model=Post, status_code=201)
def create_post(post: Post, session: Session = Depends(get_session)):
session.add(post)
session.commit()
session.refresh(post)
return post
@app.get("/posts/", response_model=List[Post])
def read_posts(session: Session = Depends(get_session)):
posts = session.exec(select(Post)).all()
return posts
@app.get("/posts/{post_id}", response_model=Post)
def read_post(post_id: int, session: Session = Depends(get_session)):
post = session.get(Post, post_id)
if not post:
raise HTTPException(status_code=404, detail="文章未找到")
return post
@app.put("/posts/{post_id}", response_model=Post)
def update_post(post_id: int, post_update: Post, session: Session = Depends(get_session)):
post = session.get(Post, post_id)
if not post:
raise HTTPException(status_code=404, detail="文章未找到")
post_data = post_update.dict(exclude_unset=True)
for key, value in post_data.items():
setattr(post, key, value)
session.add(post)
session.commit()
session.refresh(post)
return post
@app.delete("/posts/{post_id}", status_code=204)
def delete_post(post_id: int, session: Session = Depends(get_session)):
post = session.get(Post, post_id)
if not post:
raise HTTPException(status_code=404, detail="文章未找到")
session.delete(post)
session.commit()
✅ 八、运行并测试
-
启动服务:
bashuvicorn main:app --reload -
访问 Swagger UI:
-
测试流程:
- POST
/posts/创建文章 - GET
/posts/查看所有文章 - GET
/posts/1查看单篇 - PUT
/posts/1更新 - DELETE
/posts/1删除
- POST
💡 第一次运行会自动创建
blog.db文件!
✅ 九、关于异步数据库(进阶说明)
虽然本教程使用同步 SQLModel,但生产环境常需异步以提升性能。
异步方案(供未来参考):
- 使用
SQLAlchemy Core+asyncpg(PostgreSQL)或aiosqlite - 或使用 Tortoise ORM(原生异步)
示例(异步伪代码):
python
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
engine = create_async_engine("postgresql+asyncpg://...")
📌 初学者建议:先掌握同步 SQLModel,再学习异步。
✅ 十、练习任务(动手实践)
🧠 请先自己尝试完成,再查看下方答案!
任务1:添加"作者"字段
- 修改
Post模型,增加author: str - 更新所有接口,确保能正常创建/读取
任务2:只返回已发布的文章
- 新增一个路由
GET /published-posts/ - 只返回
published=True的文章
任务3(挑战):分页查询
- 修改
GET /posts/,支持查询参数skip和limit - 默认
skip=0,limit=10 - 使用
session.exec(select(Post).offset(skip).limit(limit))
✅ 十一、练习任务参考答案
✅ 任务1 答案
models.py:
python
class Post(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
title: str
content: str
author: str # 新增
published: bool = False
其他代码无需修改!SQLModel 会自动处理新字段。
✅ 任务2 答案
main.py:
python
@app.get("/published-posts/", response_model=List[Post])
def read_published_posts(session: Session = Depends(get_session)):
posts = session.exec(select(Post).where(Post.published == True)).all()
return posts
✅ 任务3 答案
main.py:
python
@app.get("/posts/", response_model=List[Post])
def read_posts(
skip: int = 0,
limit: int = 10,
session: Session = Depends(get_session)
):
posts = session.exec(select(Post).offset(skip).limit(limit)).all()
return posts
测试:
/posts/?skip=0&limit=2
✅ 十二、小结
在本部分,你学会了:
- 使用 SQLModel 定义数据库模型
- 创建 SQLite 数据库并自动建表
- 实现完整的 CRUD(增删改查) 接口
- 使用 依赖注入 管理数据库会话
- 扩展功能:条件查询、分页
你现在已经能构建一个带真实数据库的 API 应用了!