在FastAPI开发中,数据库交互是核心环节之一。作为一款主打“高效、易用、强类型”的API框架,FastAPI本身并不自带ORM(对象关系映射)工具,这就需要我们选择一款合适的ORM,将Python对象与数据库表进行关联,摆脱繁琐的原生SQL编写,提升开发效率与代码可维护性。
市面上主流的Python ORM有SQLAlchemy、Django ORM、Peewee等,但对于FastAPI开发者而言,SQLModel无疑是最优选择之一——它由FastAPI作者Sebastián Ramírez亲自开发,完美契合FastAPI的设计理念,兼顾了SQLAlchemy的强大功能与Pydantic的数据校验能力,让数据库开发与API接口开发无缝衔接,真正实现“一次定义,多处复用”。
今天,我们就以SQLModel为核心,聊聊FastAPI中的ORM选型、SQLModel的核心特性、从环境搭建到CRUD实战的完整流程,帮你快速掌握FastAPI+SQLModel的高效开发模式,解决数据库交互中的常见痛点。
一、为什么FastAPI首选SQLModel?ORM选型对比
在正式上手之前,我们先搞清楚一个问题:为什么推荐SQLModel?它与其他主流ORM相比,有哪些不可替代的优势?先看一张核心对比表,帮你快速理清选型逻辑:
| ORM工具 | 核心优势 | 与FastAPI适配度 | 适用场景 |
|---|---|---|---|
| SQLModel | 1. 原生支持Pydantic,数据校验与ORM无缝衔接;2. 基于SQLAlchemy,功能强大且灵活;3. 语法简洁,学习成本低;4. 强类型支持,契合FastAPI设计理念 | ★★★★★(完美适配,作者同源,支持自动API文档) | FastAPI各类项目,尤其适合前后端分离、API开发场景 |
| SQLAlchemy | 1. 功能全面,支持多种数据库;2. 生态成熟,社区活跃;3. 灵活性极高,可兼容原生SQL | ★★★★☆(需手动适配Pydantic,代码冗余略多) | 复杂数据库场景、大型项目,需要高度自定义数据库操作 |
| Django ORM | 1. 集成度高,开箱即用;2. 语法简洁,开发效率高;3. 内置admin后台,便于管理 | ★★★☆☆(与Django框架强绑定,单独使用适配成本高) | Django+FastAPI混合开发,或已有Django项目迁移 |
| Peewee | 1. 轻量级,依赖少;2. 语法简洁,上手快;3. 支持异步操作 | ★★★☆☆(功能相对简单,复杂场景需自定义扩展) | 轻量级API项目,数据库操作简单,追求极致轻量 |
总结来说,SQLModel的核心优势在于“适配FastAPI”——它解决了SQLAlchemy需要手动适配Pydantic的痛点,将数据模型(ORM模型)与校验模型(Pydantic模型)合二为一,一次定义既能用于数据库交互,也能用于API接口的请求/响应校验,大幅减少代码冗余,提升开发效率。对于绝大多数FastAPI项目而言,SQLModel是“兼顾易用性与功能性”的最优解。
二、SQLModel核心特性:为什么它能适配FastAPI?
SQLModel的设计理念是“SQLAlchemy + Pydantic = SQLModel”,它并非重新开发一款ORM,而是在SQLAlchemy的基础上,集成了Pydantic的核心能力,同时做了语法简化,让开发者用更少的代码完成更多的事情。其核心特性主要有4点,每一点都切中FastAPI开发的痛点:
1. 模型合一:ORM模型与Pydantic模型无缝融合
这是SQLModel最核心的特性。在传统开发中,我们需要分别定义ORM模型(用于数据库交互)和Pydantic模型(用于API数据校验),两者字段重复,需要手动同步更新,非常繁琐。而SQLModel中,一个模型类即可同时实现“ORM模型”和“Pydantic模型”的功能。
例如,定义一个用户模型,只需继承SQLModel,既可以用于创建数据库表,也可以作为API接口的请求体/响应体,自动完成数据校验,无需重复定义。
2. 强类型支持:契合FastAPI的类型提示
FastAPI的核心优势之一是“强类型提示”,而SQLModel完全遵循这一理念,所有字段都需要指定类型(如int、str、datetime),配合IDE可以实现自动补全、类型检查,减少开发错误。同时,SQLModel的类型定义与Python原生类型、Pydantic类型完全兼容,无需额外学习新的类型体系。
3. 基于SQLAlchemy:功能强大,生态兼容
SQLModel底层完全基于SQLAlchemy,意味着它继承了SQLAlchemy的所有核心功能——支持多种数据库(MySQL、PostgreSQL、SQLite等)、复杂查询、事务管理、索引优化等。同时,它与SQLAlchemy生态完全兼容,可以直接使用SQLAlchemy的原生API,灵活应对复杂数据库场景。
4. 简洁易用:降低ORM学习成本
相比SQLAlchemy繁琐的模型定义和会话管理,SQLModel做了大量语法简化,比如默认提供主键字段、简化会话创建、支持链式查询等,让开发者无需深入学习SQLAlchemy的复杂概念,就能快速上手数据库操作。即使是新手,也能在几分钟内完成基础的CRUD操作。
三、实战上手:FastAPI + SQLModel 完整流程(从0到1)
下面我们从环境搭建开始,一步步实现一个FastAPI + SQLModel的实战案例,涵盖“模型定义、数据库连接、CRUD接口开发、测试”全流程,让你快速掌握核心用法。
1. 环境准备与安装
首先,我们需要安装所需依赖,包括FastAPI、SQLModel、ASGI服务器(Uvicorn),以及数据库驱动(以SQLite为例,无需额外安装数据库,开箱即用;如果使用MySQL/PostgreSQL,需安装对应驱动)。
# 安装核心依赖
pip install fastapi sqlmodel uvicorn
# 若使用MySQL,需安装额外驱动
pip install mysql-connector-python
# 若使用PostgreSQL,需安装额外驱动
pip install psycopg2-binary
2. 核心配置:数据库连接与模型定义
我们先创建一个main.py文件,完成数据库连接配置和模型定义。这里以SQLite为例(无需配置数据库服务,文件形式存储,适合快速开发测试)。
from fastapi import FastAPI
from sqlmodel import Field, Session, SQLModel, create_engine
# 1. 数据库连接配置(SQLite)
# SQLite数据库文件存储在当前目录下,名为test.db
DATABASE_URL = "sqlite:///./test.db"
# 创建数据库引擎(engine是SQLAlchemy的核心,负责与数据库交互)
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) # SQLite需添加此参数
# 2. 定义ORM模型(同时也是Pydantic模型)
class User(SQLModel, table=True):
# 主键字段,自增,默认不为空
id: int | None = Field(default=None, primary_key=True)
# 用户名,唯一,不为空
username: str = Field(index=True, unique=True, nullable=False)
# 邮箱,唯一,不为空
email: str = Field(index=True, unique=True, nullable=False)
# 年龄,可选,默认None
age: int | None = Field(default=None)
# 3. 创建数据库表(仅首次运行时需要,创建所有继承SQLModel且table=True的模型对应的表)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
# 4. 初始化FastAPI应用
app = FastAPI(title="FastAPI + SQLModel 实战", version="1.0")
# 5. 启动时创建数据库表(在应用启动时执行一次)
@app.on_event("startup")
def on_startup():
create_db_and_tables()
# 6. 依赖项:获取数据库会话(每次请求都会创建一个新的会话,请求结束后关闭)
def get_session():
with Session(engine) as session:
yield session # 生成会话,供视图函数使用
session.close() # 请求结束后关闭会话
3. CRUD接口开发:完整数据库交互
接下来,我们基于上面定义的User模型,开发“创建用户、查询用户、更新用户、删除用户”的完整CRUD接口,感受SQLModel与FastAPI的无缝衔接。
(1)创建用户(POST接口)
接收用户提交的用户名、邮箱、年龄,验证通过后存入数据库,返回创建成功的用户信息。
from fastapi import Depends, HTTPException
from sqlmodel import select
# 创建用户
@app.post("/users/", response_model=User, status_code=201)
def create_user(user: User, session: Session = Depends(get_session)):
# 检查用户名或邮箱是否已存在
existing_user = session.exec(select(User).where(
(User.username == user.username) | (User.email == user.email)
)).first()
if existing_user:
raise HTTPException(status_code=400, detail="用户名或邮箱已存在")
# 将用户对象添加到会话
session.add(user)
# 提交会话(将数据写入数据库)
session.commit()
# 刷新用户对象(获取数据库自动生成的id等字段)
session.refresh(user)
return user
(2)查询用户(GET接口)
实现两个查询接口:查询所有用户、根据ID查询单个用户。
# 查询所有用户
@app.get("/users/", response_model=list[User])
def read_users(session: Session = Depends(get_session)):
# 执行查询,获取所有用户
users = session.exec(select(User)).all()
return users
# 根据ID查询单个用户
@app.get("/users/{user_id}", response_model=User)
def read_user(user_id: int, session: Session = Depends(get_session)):
# 执行查询,根据ID获取用户
user = session.get(User, user_id)
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
return user
(3)更新用户(PUT接口)
根据用户ID,更新用户的用户名、邮箱或年龄,验证通过后更新数据库。
# 更新用户
@app.put("/users/{user_id}", response_model=User)
def update_user(
user_id: int,
user_update: User, # 接收更新后的用户数据(Pydantic校验)
session: Session = Depends(get_session)
):
# 获取要更新的用户
user = session.get(User, user_id)
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# 检查更新后的用户名/邮箱是否与其他用户冲突
if user_update.username != user.username:
existing_username = session.exec(select(User).where(User.username == user_update.username)).first()
if existing_username:
raise HTTPException(status_code=400, detail="用户名已存在")
if user_update.email != user.email:
existing_email = session.exec(select(User).where(User.email == user_update.email)).first()
if existing_email:
raise HTTPException(status_code=400, detail="邮箱已存在")
# 更新用户字段(将user_update的非空字段赋值给user)
user_data = user_update.model_dump(exclude_unset=True) # 排除未设置的字段
for key, value in user_data.items():
setattr(user, key, value)
# 提交更新
session.add(user)
session.commit()
session.refresh(user)
return user
(4)删除用户(DELETE接口)
根据用户ID,删除数据库中的用户。
# 删除用户
@app.delete("/users/{user_id}", status_code=204)
def delete_user(user_id: int, session: Session = Depends(get_session)):
user = session.get(User, user_id)
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# 删除用户
session.delete(user)
session.commit()
return None # 204状态码无需返回内容
4. 启动应用与测试
完成接口开发后,启动应用,访问FastAPI自带的自动文档(/docs),即可测试所有CRUD接口。
# 启动命令(开启热重载,便于开发)
uvicorn main:app --reload
启动成功后,访问 http://127.0.0.1:8000/docs,即可看到自动生成的API文档,支持在线测试所有接口:
- POST /users/:提交JSON数据(username、email、age),创建用户;
- GET /users/:查看所有用户列表;
- GET /users/{user_id}:查看指定ID的用户;
- PUT /users/{user_id}:修改指定ID的用户信息;
- DELETE /users/{user_id}:删除指定ID的用户。
测试完成后,会在当前目录下生成test.db文件,这就是SQLite数据库文件,可使用SQLite可视化工具(如DB Browser for SQLite)查看数据库表和数据。
四、进阶技巧:SQLModel高级用法(提升开发效率)
除了基础的CRUD操作,SQLModel还有很多实用的高级用法,帮你应对更复杂的开发场景,进一步提升开发效率。
1. 复杂查询:过滤、排序、分页
实际开发中,我们经常需要对数据进行过滤、排序和分页,SQLModel基于SQLAlchemy的查询语法,支持链式调用,非常灵活。
# 示例:查询年龄大于18的用户,按用户名升序排序,分页(每页5条)
@app.get("/users/filter/", response_model=list[User])
def filter_users(
age: int = 18,
page: int = 1,
page_size: int = 5,
session: Session = Depends(get_session)
):
# 构建查询:过滤年龄>age,按username升序
query = select(User).where(User.age > age).order_by(User.username.asc())
# 分页:跳过前(page-1)*page_size条,取page_size条
query = query.offset((page - 1) * page_size).limit(page_size)
# 执行查询
users = session.exec(query).all()
return users
2. 关联模型:一对一、一对多关系
当数据库表之间存在关联关系(如用户与订单、文章与评论)时,SQLModel可以轻松实现关联查询。下面以“用户-订单”一对多关系为例,演示关联模型的定义与查询。
# 定义订单模型(与用户是一对多关系)
class Order(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
title: str = Field(nullable=False) # 订单标题
price: float = Field(nullable=False) # 订单价格
# 外键:关联User表的id字段
user_id: int = Field(foreign_key="user.id", nullable=False)
# 关联查询:查询指定用户的所有订单
@app.get("/users/{user_id}/orders/", response_model=list[Order])
def get_user_orders(user_id: int, session: Session = Depends(get_session)):
# 先查询用户,确认存在
user = session.get(User, user_id)
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# 查询该用户的所有订单(外键user_id=user_id)
orders = session.exec(select(Order).where(Order.user_id == user_id)).all()
return orders
3. 异步支持:提升并发性能
FastAPI主打异步性能,SQLModel也支持异步操作,只需使用异步引擎和异步会话,即可实现全链路异步数据库交互(需配合异步数据库驱动,如asyncpg for PostgreSQL、aiomysql for MySQL)。
# 异步数据库配置(以PostgreSQL为例)
from sqlmodel import create_async_engine, AsyncSession
# 异步数据库URL(需安装asyncpg)
DATABASE_URL = "postgresql+asyncpg://user:password@localhost:5432/dbname"
# 创建异步引擎
async_engine = create_async_engine(DATABASE_URL)
# 异步依赖项:获取异步会话
async def get_async_session():
async with AsyncSession(async_engine) as session:
yield session
await session.commit()
# 异步接口示例(查询所有用户)
@app.get("/async/users/", response_model=list[User])
async def async_read_users(session: AsyncSession = Depends(get_async_session)):
# 异步查询
result = await session.exec(select(User))
users = result.all()
return users
五、常见误区与避坑指南
在使用FastAPI + SQLModel开发时,很多开发者会因对细节不熟悉而踩坑,总结4个最常见的误区,帮你快速避坑:
1. 误区1:忽略会话管理,导致数据无法提交
SQLModel的会话(Session)是数据库交互的核心,所有数据库操作(添加、修改、删除)都需要通过会话提交,否则数据不会写入数据库。切记:session.add() 只是将对象添加到会话缓存,必须调用 session.commit() 才能真正写入数据库。
2. 误区2:模型字段未设置nullable,导致插入失败
SQLModel中,字段默认 nullable=False(不为空),如果插入数据时未提供该字段,会导致数据库插入失败。如果某个字段是可选的,必须显式设置 nullable=True 或 default=None。
3. 误区3:混淆同步与异步引擎,导致报错
同步引擎(create_engine)对应同步会话(Session),异步引擎(create_async_engine)对应异步会话(AsyncSession),两者不能混用。如果使用异步接口,必须搭配异步引擎和异步会话,否则会报“同步会话不能在异步函数中使用”的错误。
4. 误区4:忘记创建数据库表,导致查询失败
SQLModel不会自动创建数据库表,必须调用SQLModel.metadata.create_all(engine) 才能创建所有模型对应的表。建议在应用启动时执行一次,避免因表不存在导致查询、插入失败。
六、总结:FastAPI + SQLModel 高效开发范式
FastAPI的核心是“高效、易用、强类型”,而SQLModel完美契合这一理念,它将ORM模型与Pydantic模型合二为一,简化了数据库交互与数据校验的流程,让开发者可以专注于业务逻辑,而非繁琐的重复代码。
通过本文的实战教程,我们可以总结出FastAPI + SQLModel的高效开发范式:
- 定义模型:继承SQLModel,一次定义,同时实现ORM与Pydantic功能;
- 配置数据库:创建引擎,定义会话依赖项,确保每次请求的会话隔离;
- 开发接口:利用FastAPI的依赖注入获取会话,通过SQLModel实现CRUD操作;
- 测试优化:利用FastAPI自动文档测试接口,根据需求使用高级特性(关联查询、异步操作)提升性能。
对于FastAPI开发者而言,SQLModel不仅是一款ORM工具,更是提升开发效率的“利器”——它让数据库开发变得简单、高效,同时兼顾了灵活性和扩展性,无论是小型API项目,还是大型复杂微服务,都能轻松应对。