FastAPI + SQLAlchemy ORM 实战:实现增删改操作

3 阅读8分钟

在FastAPI与SQLAlchemy ORM的协同开发中,除了查询操作,数据的新增、更新、删除(增删改)也是数据库交互的核心场景。基于书籍表模型,我们可通过ORM提供的简洁API,无需编写原生SQL,就能规范、高效地完成数据的增删改操作。本文将严格按照指定的文件命名逻辑,分3个核心场景拆解增删改操作,每个场景对应独立的实现代码与思路,全程结合可直接运行的示例,仅围绕增删改操作核心展开,确保内容简洁、可落地,助力开发者快速掌握各类数据操作技巧。

一、通用前置配置(所有增删改场景共用)

所有增删改操作均依赖统一的基础配置,包括数据库引擎创建、模型定义、会话管理及建表操作,确保操作能正常连接数据库并映射到书籍表。以下是完整的通用配置代码,可直接复制运行,适配后续所有增删改场景。

from datetime import datetime
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import DateTime, func, String, Float, select
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from pydantic import BaseModel  # 用于请求体参数校验

# 初始化FastAPI应用
app = FastAPI(debug=True)

# 1. 创建异步数据库引擎(适配MySQL,修改为自身数据库配置)
ASYNC_DATABASE_URL = "mysql+aiomysql://root:654321@localhost:3306/book_manage?charset=utf8"
async_engine = create_async_engine(
    ASYNC_DATABASE_URL,
    echo=True,          # 输出SQL日志,便于调试增删改语句
    pool_size=8,        # 连接池活跃连接数,适配并发操作
    max_overflow=15     # 额外允许的临时连接数
)

# 2. 定义模型基类(包含公共字段)
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="修改时间"
    )

# 3. 定义书籍表模型(增删改操作的核心载体)
class Book(Base):
    __tablename__ = "book"  # 数据表名,与数据库中表一致

    id: Mapped[int] = mapped_column(primary_key=True, comment="书籍主键ID")
    book_title: Mapped[str] = mapped_column(String(255), comment="书籍标题")
    book_author: Mapped[str] = mapped_column(String(100), comment="书籍作者")
    book_price: Mapped[float] = mapped_column(Float, comment="书籍售价")
    book_publisher: Mapped[str] = mapped_column(String(255), comment="出版社")
    book_isbn: Mapped[str] = mapped_column(String(50), unique=True, comment="书籍ISBN编号(唯一)")

# 4. 建表操作(确保书籍表已存在,为增删改提供数据载体)
async def create_book_table():
    async with async_engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

# 启动FastAPI时自动建表
@app.on_event("startup")
async def startup():
    await create_book_table()

# 5. 创建异步会话工厂与依赖项(增删改操作必须通过会话实现)
AsyncSessionLocal = async_sessionmaker(
    bind=async_engine,
    class_=AsyncSession,
    expire_on_commit=False  # 提交后会话不过期,提升操作效率
)

# 会话依赖项:自动管理会话的创建、提交、回滚与关闭,适配所有增删改操作
async def get_db_session():
    async with AsyncSessionLocal() as session:
        try:
            yield session  # 将会话传递给路由操作函数
            await session.commit()  # 操作成功,提交事务
        except Exception:
            await session.rollback()  # 操作失败,回滚事务
            raise
        finally:
            await session.close()  # 无论成功失败,均关闭会话

# 基础测试接口,验证配置是否正常
@app.get("/")
async def index():
    return {"status": "success", "message": "增删改环境配置完成,可进行各类数据操作"}

二、分场景增删改实现

场景1:新增数据

新增数据是将用户输入的书籍信息(如标题、作者、价格等)提交到数据库,核心是接收用户输入、校验参数、创建ORM对象并提交到数据库。通过Pydantic定义请求体模型,可实现参数校验,避免无效数据入库。

# 1. 定义请求体模型(用于接收用户输入的书籍信息,实现参数校验)
class BookCreate(BaseModel):
    id: int  # 书籍主键ID,需用户手动输入(也可配置自增,按需调整)
    book_title: str  # 书籍标题,必填
    book_author: str  # 书籍作者,必填
    book_price: float  # 书籍售价,必填,需为浮点数
    book_publisher: str  # 出版社,必填
    book_isbn: str  # ISBN编号,必填,需唯一

# 2. 新增书籍接口:接收用户输入,创建书籍数据
@app.post("/book/add_book")
async def add_book(book: BookCreate, db: AsyncSession = Depends(get_db_session)):
    # 可选:校验ISBN编号是否已存在,避免重复入库
    existing_book = await db.execute(select(Book).where(Book.book_isbn == book.book_isbn))
    if existing_book.scalar_one_or_none():
        raise HTTPException(status_code=400, detail="该ISBN编号已存在,无法重复新增")
    
    # 核心操作:将请求体数据转换为ORM对象
    book_obj = Book(**book.model_dump())  # model_dump()将Pydantic模型转为字典,适配ORM对象创建
    db.add(book_obj)  # 将ORM对象添加到会话
    await db.commit()  # 提交事务,完成数据新增
    await db.refresh(book_obj)  # 刷新对象,获取数据库自动填充的字段(如create_time)
    return {
        "status": "success",
        "message": "书籍新增成功",
        "data": book_obj
    }

关键说明:新增操作核心依赖db.add()await db.commit(),前者将ORM对象添加到会话,后者提交事务完成入库;Pydantic模型用于校验用户输入,避免无效数据(如非浮点数价格);可选的重复校验的,可提升数据唯一性。

场景2:更新数据

更新数据的核心逻辑是“先查询、再修改、最后提交”,即根据书籍主键ID查询对应数据,确认数据存在后,修改指定字段的值,最后提交事务完成更新。若查询不到对应数据,需返回友好的异常提示。

# 1. 定义请求体模型(用于接收用户输入的更新数据,无需传入ID,ID通过路径参数获取)
class BookUpdate(BaseModel):
    book_title: str  # 书籍标题,必填
    book_author: str  # 书籍作者,必填
    book_price: float  # 书籍售价,必填
    book_publisher: str  # 出版社,必填
    book_isbn: str  # ISBN编号,必填,需唯一

# 2. 更新书籍接口:根据ID查询,再修改数据
@app.put("/book/update_book/{book_id}")
async def update_book(
    book_id: int,  # 路径参数:书籍主键ID,用于查询待更新数据
    book_data: BookUpdate,  # 请求体:更新后的书籍信息
    db: AsyncSession = Depends(get_db_session)
):
    # 步骤1:根据ID查询待更新的书籍
    db_book = await db.get(Book, book_id)
    # 若查询不到数据,抛出404异常
    if not db_book:
        raise HTTPException(status_code=404, detail=f"未查询到ID为{book_id}的书籍,无法更新")
    
    # 步骤2:校验更新的ISBN编号是否与其他书籍重复(排除自身)
    existing_isbn = await db.execute(
        select(Book).where((Book.book_isbn == book_data.book_isbn) & (Book.id != book_id))
    )
    if existing_isbn.scalar_one_or_none():
        raise HTTPException(status_code=400, detail="该ISBN编号已被其他书籍使用,无法更新")
    
    # 步骤3:修改书籍字段值(将请求体数据赋值给ORM对象)
    db_book.book_title = book_data.book_title
    db_book.book_author = book_data.book_author
    db_book.book_price = book_data.book_price
    db_book.book_publisher = book_data.book_publisher
    db_book.book_isbn = book_data.book_isbn
    
    # 步骤4:提交事务,完成更新
    await db.commit()
    await db.refresh(db_book)  # 刷新对象,获取更新后的修改时间
    return {
        "status": "success",
        "message": f"ID为{book_id}的书籍更新成功",
        "data": db_book
    }

关键说明:更新操作需遵循“先查后改”原则,通过db.get()根据主键查询数据;修改字段时直接给ORM对象赋值即可;提交事务后,通过db.refresh()可获取数据库自动更新的字段(如update_time)。

场景3:删除数据

删除数据的核心逻辑与更新类似,同样是“先查询、再删除、最后提交”,即根据书籍主键ID查询对应数据,确认数据存在后,删除该数据并提交事务。删除操作不可逆,需做好查询校验,避免误删。

# 删除书籍接口:根据ID查询,再删除数据
@app.delete("/book/delete_book/{book_id}")
async def delete_book(book_id: int, db: AsyncSession = Depends(get_db_session)):
    # 步骤1:根据ID查询待删除的书籍
    db_book = await db.get(Book, book_id)
    # 若查询不到数据,抛出404异常
    if not db_book:
        raise HTTPException(status_code=404, detail=f"未查询到ID为{book_id}的书籍,无法删除")
    
    # 步骤2:删除ORM对象(从会话中移除)
    await db.delete(db_book)
    # 步骤3:提交事务,完成删除
    await db.commit()
    
    # 删除成功,返回提示信息(无需返回删除的数据,避免冗余)
    return {
        "status": "success",
        "message": f"ID为{book_id}的书籍删除成功"
    }

关键说明:删除操作核心依赖await db.delete(db_book)await db.commit(),前者从会话中删除ORM对象,后者提交事务完成数据库层面的删除;删除前必须校验数据是否存在,避免无效删除操作。

三、增删改操作注意事项

  1. 所有增删改操作必须通过异步会话实现,不可直接操作引擎,避免出现连接泄露或阻塞事件循环;

  2. 新增和更新操作中,需做好数据校验(如必填字段、数据类型、唯一性),避免无效数据或重复数据入库;

  3. 更新和删除操作必须遵循“先查后改/删”原则,先确认数据存在,再执行后续操作,提升用户体验;

  4. 事务管理至关重要,增删改操作需确保“提交事务”和“异常回滚”,避免出现数据不一致的情况;

  5. 删除操作不可逆,实际开发中可根据需求添加二次确认逻辑,或采用“软删除”(添加删除标记)替代物理删除。

总结

通过SQLAlchemy ORM的增删改API,我们无需编写复杂的原生SQL,即可实现高效、规范的数据操作,同时结合Pydantic的参数校验和FastAPI的异常处理,让操作更安全、更易维护。在实际开发中,可根据具体业务需求,灵活调整参数校验规则和操作逻辑,适配不同的业务场景。