【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 防止"对象失效"问题
相关推荐
2601_9584925519 小时前
Optimizing Engagement with Freehead Skate - HTML5 Game - Construct 3
前端·html·html5
Li emily19 小时前
解决了加密货币api多币种订阅时的数据乱序问题
人工智能·python·api·fastapi
茉莉玫瑰花茶19 小时前
工作流的常见模式 [ 1 ]
java·服务器·前端
zhangxingchao20 小时前
AI应用开发六:企业知识库
前端·人工智能·后端
山峰哥20 小时前
SQL慢查询调优实战:从全表扫描到索引覆盖的完整复盘
前端·数据库·sql·性能优化
红尘散仙21 小时前
一个 `#[uniffi::export]`,把 Rust 接进 React Native
前端·后端·rust
moshuying21 小时前
AI Coding 最大的 token 黑洞,可能根本不是 prompt
前端
红尘散仙21 小时前
一行 `#[specta::specta]`,让 Tauri IPC 有类型
前端·后端·rust
lichenyang45321 小时前
HarmonyOS HMRouter 接入记录:从普通 Tab Demo 到路由跳转
前端