FastAPI 学习教程 · 第5部分

异步编程与数据库集成(SQLModel + SQLAlchemy)

💡 本部分目标:掌握在 FastAPI 中使用异步方式连接数据库;学会使用 SQLModel(由 FastAPI 作者开发)定义模型、操作数据,构建真实可运行的博客系统。


✅ 一、为什么需要数据库?

前几部分我们使用"模拟数据",但真实应用必须持久化数据。FastAPI 官方推荐使用:

  • SQLModel:结合 Pydantic(用于 API)和 SQLAlchemy(用于数据库)的库
  • 支持 SQLite(开发)、PostgreSQL、MySQL 等
  • 代码简洁,类型安全,与 FastAPI 无缝集成

🔗 官网:https://sqlmodel.tiangolo.com/


✅ 二、安装依赖

在你的虚拟环境中运行:

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

python 复制代码
engine = 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()

✅ 八、运行并测试

  1. 启动服务:

    bash 复制代码
    uvicorn main:app --reload
  2. 访问 Swagger UI:

    👉 http://127.0.0.1:8000/docs

  3. 测试流程:

    • POST /posts/ 创建文章
    • GET /posts/ 查看所有文章
    • GET /posts/1 查看单篇
    • PUT /posts/1 更新
    • DELETE /posts/1 删除

💡 第一次运行会自动创建 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/,支持查询参数 skiplimit
  • 默认 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 应用了!

相关推荐
风送雨2 小时前
FastAPI 学习教程 · 第6部分
学习·fastapi
副露のmagic2 小时前
更弱智的算法学习 day48
学习·算法
Nan_Shu_6142 小时前
学习: Threejs (15)& Threejs (16)
学习·three.js
知识分享小能手2 小时前
Oracle 19c入门学习教程,从入门到精通,Oracle 过程、函数、触发器和包详解(7)
数据库·学习·oracle
程序员敲代码吗2 小时前
如何从Python初学者进阶为专家?
jvm·数据库·python
漏刻有时2 小时前
微信小程序学习实录14:微信小程序手写签名功能完整开发方案
学习·微信小程序·notepad++
魔芋红茶3 小时前
Spring Security 学习笔记 1:快速开始
笔记·学习·spring
皮蛋sol周3 小时前
嵌入式学习数据结构(三)栈 链式 循环队列
arm开发·数据结构·学习·算法··循环队列·链式队列
Kratzdisteln3 小时前
【1902】优化后的三路径学习系统
android·学习