【FastAPI】 + SQLAlchemy 异步 ORM 实现完整 CRUD 操作

从零实战:FastAPI + SQLAlchemy 异步 ORM 实现完整 CRUD 操作(附完整代码)

一、为什么要学「FastAPI + SQLAlchemy 异步 ORM」?

在现代 Web 服务中,数据库是核心组件。然而,传统同步操作(如 MySQLdb)在高并发场景下容易成为瓶颈。

FastAPI + SQLAlchemy 异步 ORM 的组合,具备以下优势:

  • ✅ 高性能(异步非阻塞 I/O)
  • ✅ 代码简洁(面向对象建模)
  • ✅ 安全可靠(SQL 注入防护)
  • ✅ 自动生成 API 文档(Swagger UI)
  • ✅ 与 Pydantic 完美结合,实现数据验证

本文将基于 你今天学习的完整内容 ,带你从零实现一个完整的 图书管理系统 的 CRUD 功能!


二、环境准备(开发前置)

bash 复制代码
pip install fastapi uvicorn sqlalchemy sqlalchemy-asyncio aiomysql pydantic

✅ 推荐使用 uvicorn 启动服务:

bash 复制代码
uvicorn main:app --reload

三、核心步骤一:定义模型类(ORM 映射)

python 复制代码
from sqlalchemy import DateTime, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from datetime import datetime

class Base(DeclarativeBase):
    # 自动生成创建与更新时间
    create_time: Mapped[datetime] = mapped_column(
        DateTime,
        insert_default=func.now(),
        default=func.now(),
        comment="创建时间"
    )
    update_time: Mapped[datetime] = mapped_column(
        DateTime,
        insert_default=func.now(),
        default=func.now(),
        onupdate=func.now(),
        comment="更新时间"
    )

class Book(Base):
    __tablename__ = "book"

    id: Mapped[int] = mapped_column(primary_key=True, comment="ID")
    name: Mapped[str] = mapped_column(String(255), comment="书名")
    author: Mapped[str] = mapped_column(String(255), comment="作者")
    price: Mapped[float] = mapped_column(Float, comment="价格")
    publisher: Mapped[str] = mapped_column(String(255), comment="出版社")

class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True, comment="ID")
    username: Mapped[str] = mapped_column(String(255), comment="用户名")
    password: Mapped[str] = mapped_column(String(255), comment="密码")
    email: Mapped[str] = mapped_column(String(255), comment="邮箱")

✅ ✅ 小技巧:所有模型继承 Base,自动拥有时间字段!


四、核心步骤二:创建数据库引擎 & 会话工厂

python 复制代码
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker

ASYNC_DATABASE_URL = "mysql+aiomysql://root:123456@localhost:3306/FastAPI_first?charset=utf8mb4"

async_engine = create_async_engine(
    ASYNC_DATABASE_URL,
    echo=True,
    pool_size=20,
    max_overflow=10,
    pool_recycle=3600
)

# 创建会话工厂
Async_sessionLocal = async_sessionmaker(
    bind=async_engine,
    class_=AsyncSession,
    expire_on_commit=False  # 关键!避免提交后对象失效!
)

五、核心步骤三:依赖注入 ------ 获取数据库会话

python 复制代码
from fastapi import Depends, HTTPException

async def get_db():
    async with Async_sessionLocal() as session:
        try:
            yield session
        except Exception:
            await session.rollback()
            raise
        finally:
            await session.close()

从此,每个路由函数都可以通过 db: AsyncSession = Depends(get_db) 自动获取数据库连接!


六、核心步骤四:CRUD 完整路由实现(精华!)

1. 查询所有书籍(GET /book/books)

python 复制代码
@app.get("/book/books")
async def get_book_list(db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(Book))
    books = result.scalars().all()
    return books

2. 查询单个书籍(按 ID)

python 复制代码
@app.get("/book/book/{id}")
async def get_book_by_id(id: int, db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(Book).where(Book.id == id))
    book = result.scalars().first()
    if not book:
        raise HTTPException(status_code=404, detail="书籍不存在")
    return book

3. 模糊查询(LIKE)

python 复制代码
@app.get("/book/search_book_like")
async def search_book_like(name: str, db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(Book).where(Book.name.like(f"%{name}%")))
    books = result.scalars().all()
    return books

4. 聚合查询(MAX、SUM、AVG)

python 复制代码
@app.get("/book/count")
async def get_book_count(db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(func.sum(Book.price)))
    total_price = result.scalar()
    return {"total_price": total_price}

5. 分页查询(OFFSET + LIMIT)

python 复制代码
@app.get("/book/page")
async def get_page(
    page: int = 1,
    page_size: int = 3,
    db: AsyncSession = Depends(get_db)
):
    result = await db.execute(
        select(Book)
        .offset((page - 1) * page_size)
        .limit(page_size)
    )
    books = result.scalars().all()
    return books

6. 增加书籍(POST /book/add_book)

python 复制代码
from pydantic import BaseModel

class BookCreate(BaseModel):
    name: str
    author: str
    price: float
    publisher: str

@app.post("/book/add_book")
async def add_book(book: BookCreate, db: AsyncSession = Depends(get_db)):
    book_obj = Book(**book.dict())
    db.add(book_obj)
    await db.commit()
    await db.refresh(book_obj)
    return book_obj

自动校验类型,无须手动判断数据合法性


7. 更新书籍(PUT /book/update_book/{id})

python 复制代码
class BookUpdate(BaseModel):
    name: str
    author: str
    price: float
    publisher: str

@app.put("/book/update_book/{id}")
async def update_book(id: int, book: BookUpdate, db: AsyncSession = Depends(get_db)):
    book_obj = await db.get(Book, id)
    if not book_obj:
        raise HTTPException(status_code=404, detail="书籍不存在")
    
    book_obj.name = book.name
    book_obj.author = book.author
    book_obj.price = book.price
    book_obj.publisher = book.publisher

    await db.commit()
    return book_obj

8. 删除书籍(DELETE /book/delete_book/{id})

python 复制代码
@app.delete("/book/delete_book/{id}")
async def delete_book(id: int, db: AsyncSession = Depends(get_db)):
    book_obj = await db.get(Book, id)
    if not book_obj:
        raise HTTPException(status_code=404, detail="书籍不存在")
    
    await db.delete(book_obj)
    await db.commit()
    return {"message": "删除成功"}

七、核心亮点总结

技能 说明
Depends(get_db) 自动注入数据库会话,避免重复代码
select().where() SQL 查询构建器,比原生 SQL 更安全
scalars().all() 提取 ORM 对象列表
await db.commit() 提交事务
Pydantic Model 请求数据自动校验
HTTPException 统一错误处理
expire_on_commit=False 防止"对象失效"问题
相关推荐
kyriewen2 小时前
Anthropic 估值逼近万亿美元,Claude Sonnet 5 + Claude Science 一天两连发
前端·ai编程·claude
小徐_23334 小时前
Wot UI 2.2.0 发布:Button 新增 subtle,VideoPreview 预览体验继续增强
前端·微信小程序·uni-app
天蓝色的鱼鱼6 小时前
关于 CSS 你可能不知道的属性,但关键时刻很有用
前端·css
泯泷7 小时前
第 2 篇:设计第一套字节码:Opcode、Instruction 与 Constant Pool
前端·javascript·安全
妙码生花7 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十五):优化细节、网络请求封装
前端·后端·ai编程
泯泷7 小时前
第 1 篇:从 1 + 2 开始:亲手写出第一台 JSVM
前端·javascript·安全
团团崽_七分甜7 小时前
Spring Boot 核心知识点总结
前端
lichenyang4537 小时前
从一个按钮开始,理解 ASCF 框架到底在做什么
前端
古夕7 小时前
第三方 SSO 接入实践:redirect_uri 编码、回调一致性与跨项目联调
前端·vue.js