FastAPI学习笔记:二、ORM
ORM(Object-RelationalMapping,对象关系映射)是一种编程技术,用于在面向对象编程语言和关系型数据库之间建立映射。它允许开发者通过操作对象的方式与数据库进行交互,而无需直接编写复杂的SQL语句。
优势:
-
减少重复的 SQL 代码
-
代码更简洁易读
-
自动处理数据库连接和事务
-
自动防止 SQL 注入攻击
ORM 分类表
| 排名 | ORM 工具 | 特点 | 适应场景 |
|---|---|---|---|
| 1 | SQLAlchemy ORM | 功能最强、最灵活、企业级 | 各类 API、微服务、数据应用 |
| 2 | Django ORM | 封装好、上手快 | Django 项目、管理后台 |
| 3 | Tortoise ORM | 全异步 | 异步 Web 服务、高并发 API |
1、ORM 使用流程
安装依赖
打开终端输入命令:
pip install sqlalchemy[asyncio] aiomysql
-
sqlalchemy[asyncio]:SQLAlchemy 的异步支持版本,适配 FastAPI 异步场景 -
aiomysql:MySQL 的异步数据库驱动,和异步 ORM 配套使用
1. 创建异步引擎、建库、建表
创建异步引擎
from sqlalchemy.ext.asyncio import create_async_engine
ASYNC_DATABASE_URL = "mysql+aiomysql://root:123456@localhost:3306/fastAPI_first?charset=utf8"
# 创建异步引擎
async_engine = create_async_engine(
ASYNC_DATABASE_URL,
echo=True, # 可选:输出SQL日志
pool_size=10, # 设置连接池中保持的持久连接数
max_overflow=20 # 设置连接池允许创建的额外连接数
)
(1)create_async_engine 是什么?
它是 SQLAlchemy 提供的异步数据库引擎创建方法,专门用于 FastAPI 这类异步 Web 项目,配合 aiomysql 驱动实现异步数据库操作。
(2)数据库 URL 解析
mysql+aiomysql://root:123456@localhost
-
mysql+aiomysql:指定使用 MySQL 数据库 + 异步aiomysql驱动 -
root:123456:数据库账号密码 -
localhost:3306:数据库地址和端口 -
fastAPI_first:目标数据库名 -
charset=utf8:指定字符集
(3)参数
| 参数 | 作用 | 场景 |
|---|---|---|
echo=True |
控制台打印所有执行的 SQL 语句 | 开发调试用,生产环境建议关闭 |
pool_size=10 |
连接池保持的持久连接数 | 高并发场景建议根据业务量调大 |
max_overflow=20 |
连接池允许的临时额外连接数 | 峰值流量时可临时扩容,用完自动回收 |
2. 建库建表
首先自己在数据库手动创建一个名为fastAPI_first的库
接着:
from datetime import datetime
from fastapi import FastAPI
from sqlalchemy import func, Float, DateTime, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.ext.asyncio import create_async_engine
ASYNC_DATABASE_URL = "mysql+aiomysql://root:1234@localhost:3306/fastAPI_first?charset=utf8"
#记得更换为自己的数据库账号密码
app= FastAPI()
# 1、 创建异步引擎
async_engine = create_async_engine(
ASYNC_DATABASE_URL,
echo=True, # 可选:输出SQL日志
pool_size=10, # 设置连接池中保持的持久连接数
)
# ----------------------------------------------------------------------
# 2. 定义模型类: 基类 + 表对应的模型类
# 基类:创建时间、更新时间;书籍表:id、书名、作者、价格、出版社
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())
# 定义公共基类 Base
# 定义了所有表的公共字段:create_time(创建时间)、update_time(更新时间)
# 配置了时间自动管理:
# insert_default=func.now():插入数据时自动填充当前时间
# onupdate=func.now():数据更新时自动刷新时间
# 其他表模型继承 Base 后,会自动带上这两个字段,避免重复代码
class Book(Base):
__tablename__ = "book"
id: Mapped[int] = mapped_column(primary_key=True, comment="书籍id")
bookname: 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="出版社")
# __tablename__ = "book":声明该类对应数据库中的 book 表
# 定义了书籍表的业务字段:id(主键)、书名、作者、价格、出版社
# 继承了 Base 的 create_time 和 update_time 字段
# 用 comment 给每个字段添加了数据库注释,方便维护
# ----------------------------------------------------------------------
# 3. 建表:定义函数建表 → FastAPI 启动的时候调用建表的函数
async def create_tables():
# 获取异步引擎,创建事务 - 建表
async with async_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all) # Base 模型类的元数据创建
# async with async_engine.begin():开启一个数据库事务,保证建表操作的原子性
# conn.run_sync(Base.metadata.create_all):
# Base.metadata 是所有 ORM 模型(比如写的 Book 表)的元数据集合
# create_all 会根据这些元数据,自动生成并执行 CREATE TABLE SQL 语句,在数据库中创建所有定义好的表
# run_sync 是异步 SQLAlchemy 的写法,用来在异步环境中执行同步的建表操作
@app.on_event("startup")
async def startup_event():
await create_tables()
# @app.on_event("startup") 是 FastAPI 的启动事件钩子
# 作用:在服务启动时自动调用 create_tables() 建表
# 效果:你一运行 FastAPI 服务,它就会自动检查并创建所有模型对应的表,不用手动执行 SQL
@app.get("/")
async def root():
return {"message": "Hello World"}
run_sync(Base.metadata.create_all) 是关键:它会把 ORM 模型同步转换为数据库表结构,自动执行建表语句。
接着启动FastAPI程序
然后登录数据库,查看表是否成功创建。
注意:因为这里的on_event 在 FastAPI 的新版本中已经被弃用了。应该使用 lifespan 事件处理器来替代。
3. 操作数据(CRUD)
ORM 的核心能力就是用 Python 代码替代原生 SQL,完成数据操作:
-
查询:用
select()语法筛选数据,无需写SELECT -
新增:创建模型对象,添加到会话并提交
-
修改:查询到对象后修改属性,提交会话
-
删除:查询到对象后从会话删除并提交
2、路由匹配中使用 ORM
我们有一个搜索的功能,用户点击搜索的时候,完成的是数据库查询的工作。
那么当我们点击注册的功能时,需要在数据库表中新出入一条数据。
那么在一个项目当时都是通过一个接口来实现数据库的增删改查的。接下来就用到了路由匹配中使用ORM了
核心:创建依赖项,使用 Depends 注入到处理函数
创建方式如下:
# 创建异步会话工厂
AsyncSessionLocal = async_sessionmaker(
bind=async_engine, # 绑定数据库引擎
class_=AsyncSession, # 指定会话类
expire_on_commit=False # 会话对象不过期,不重新查询数据库
)
# 依赖项,用于获取数据库会话
async def get_database():
async with AsyncSessionLocal() as session:
try:
yield session # 返回数据库会话给路由处理函数
await session.commit() # 无异常,提交事务
except Exception:
await session.rollback() # 有异常则回滚
raise
finally:
await session.close() # 关闭会话
@app.get("/book/books")
async def get_book_list(
db: AsyncSession = Depends(get_database)#依赖注入数据库对象
):
# 查询所有书籍
result = await db.execute(select(Book)) # Book 模型类
user = result.scalars().all()#查询所有
return user
直接查询book表中的数据
from datetime import datetime
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends
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
# 创建 FastAPI 应用实例,使用 lifespan 管理应用生命周期
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
应用生命周期管理器
- yield 之前:应用启动时执行(如:数据库初始化、连接池创建等)
- yield 之后:应用关闭时执行(如:关闭数据库连接、清理资源等)
"""
# 启动时创建数据库表
await create_tables()
yield
# 如果有关闭时需要执行的清理操作,可以在这里添加
app = FastAPI(lifespan=lifespan)
# 1、创建异步引擎
ASYNC_DATABASE_URL = "mysql+aiomysql://root:123456@localhost:3306/fastapi_first?charset=utf8"
async_engine = create_async_engine(
ASYNC_DATABASE_URL,
echo=True, # 可选,输出SQL日志
pool_size=10, # 设置连接池活跃的连接数
max_overflow=20 # 允许额外的连接数
)
# 2、定义模型类: 基类 + 表对应的模型类
# 基类:创建时间、更新时间;书籍表:id、书名、作者、价格、出版社
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") # 主键
bookname: 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="出版社") # 出版社
# 3、建表:定义函数建表->FastAPI启动时调用建表的函数
async def create_tables():
"""
创建数据库表
使用异步引擎创建所有继承自 Base 的模型类对应的表
"""
# 获取异步引擎,创建事务-建表
async with async_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all) # Base 模型类的元数据创建
@app.get("/")
async def root():
"""根路径接口"""
return {"message": "Hello World"}
# 需求:查询功能的接口,查询图书->依赖注入:创建依赖项获取数据库会话 + Depends 注入路由处理函数
# 创建异步会话工厂
AsyncSessionLocal = async_sessionmaker(
bind = async_engine, # 绑定上面的数据库引擎
class_=AsyncSession, # 指定会话类
expire_on_commit=False # 提交后不关闭会话,不会重新查询数据库
)
# 创建依赖项:获取数据库会话
async def get_database():
async with AsyncSessionLocal() as session:
try:
yield session # 返回数据会话给路由处理函数
await session.commit() # 提交事务
except Exception as e:
await session.rollback() # 有异常则回滚事务
raise e
finally:
await session.close() # 关闭会话
@app.get("/book/books")
async def get_book_list(db: AsyncSession = Depends(get_database)):
"""
就是这里为什么传递的依赖是get_database,而不是get_database()函数呢?
✅ 不加括号:传递函数本身,让 FastAPI 在合适的时机调用
❌ 加括号:立即执行函数,传递的是返回值(这会导致错误)
这是 FastAPI 依赖注入的核心机制,通过传递函数引用,框架可以控制何时调用、如何管理生命周期(比如处理 yield 前后的逻辑)。
"""
# 查询
result = await db.execute(select(Book))
book = result.scalars().all()
return book
这里是空的,因为我们还没有往数据库表中插入数据
在PyCharm中连接数据库,然后使用图形化工具插入一条数据
然后重新进入到接口文档,刷新,再次发起请求进行查询
3、ORM数据库操作
先插入几条书籍的数据
INSERT INTO fastapi_first.book (bookname, author, price, publisher, create_time, update_time)
VALUES
('活着', '余华', 39.0, '作家出版社', NOW(), NOW()),
('平凡的世界', '路遥', 78.0, '北京十月文艺出版社', NOW(), NOW()),
('百年孤独', '加西亚·马尔克斯', 56.8, '南海出版公司', NOW(), NOW()),
('解忧杂货店', '东野圭吾', 42.0, '南海出版公司', NOW(), NOW());
(1)查询
核心语句:await db.execute( select(模型类) ),返回一个 ORM 对象
获取所有数据:scalars().all()
@app.get("/book/get_books")
async def get_book_list(db: AsyncSession=Depends(get_database)):
result = await db.execute(select(Book))
book = result.scalars().all()
return book
获取单条数据:scalars().first() ;get(模型类, 主键值)
@app.get("/book/get_book")
async def get_book(db: AsyncSession=Depends(get_database)):
# result = await db.execute(select(Book))#获取DEM对象
# book=result.scalars().first()#获取对象第一条数据
book = await db.get(Book, 1)#根据主键来获取单条数据
return book
更改这个方法get_book_list
@app.get("/book/books")
async def get_book_list(db: AsyncSession = Depends(get_database)):
# 查询所有图书
result = await db.execute(select(Book))
books = result.scalars().all()
return books
查询全部数据、查询第一条数据
@app.get("/book/books")
async def get_book_list(db: AsyncSession = Depends(get_database)):
# 查询指定的图书
result = await db.execute(select(Book))
# books = result.scalars().all() # 获取所有结果
books =result.scalars().first()
return books
查询指定的图书
@app.get("/book/books")
async def get_book_list(db: AsyncSession = Depends(get_database)):
# 查询指定的图书
# result = await db.execute(select(Book))
# books = result.scalars().all() # 获取所有结果
# books =result.scalars().first()
book = await db.get(Book,4)
return book
查询条件
select(Book).where(条件, 条件2, ...)
条件:
-
比较判断:==; >; <; >=; <= 等
-
模糊查询:like()
-
与非查询:&; |; ~
-
包含查询:in_()
比较判断
-
比较判断:==; >; <; >=; <= 等
# 需求:路径参数 书籍id
@app.get("/book/get_book/{book_id}")
async def get_book_list(book_id,db: AsyncSession = Depends(get_database)):
result = await db.execute(select(Book).where(Book.id == book_id))
book = result.scalar_one_or_none()
return book
# 需求:条件 价格大于等于200
@app.get("/book/get_book_price/{price}")
async def get_book_price(price,db: AsyncSession = Depends(get_database)):
result = await db.execute(select(Book).where(Book.price >= price))
books = result.scalars().all()
return books
模糊查询
模糊查询:like()
-
%:零个、一个或多个字符
-
_:一个单个字符
# 需求:作者以 曹 开头 % _
@app.get("/book/get_book_by_author")
async def get_book_list(db: AsyncSession = Depends(get_database)):
result = await db.execute(select(Book).where(Book.author.like("曹%")))
book = result.scalars().all()
return book
@app.get("/book/get_book_by_author")
async def get_book_list(db: AsyncSession = Depends(get_database)):
result = await db.execute(select(Book).where(Book.author.like("曹_"))) # 这个只能查询两个字并且姓曹的人
book = result.scalars().all()
return book
与非查询
与非查询:
-
&:与
-
|:或
-
~:非
# 需求:作者以 曹 开头 % _
@app.get("/book/get_book_by_author")
async def get_book_list(db: AsyncSession = Depends(get_database)):
# result = await db.execute(select(Book).where(Book.author.like("曹%")))
result = await db.execute(select(Book).where(Book.author.like("曹%") & (Book.price>100)))
result = await db.execute(select(Book).where(Book.author.like("曹%") | (Book.price>100)))
result = await db.execute(select(Book).where(~Book.author.like("曹%")))
book = result.scalars().all()
return book
包含查询
-
in_()
@app.get("/book/get_book_by_author")
async def get_book_list(db: AsyncSession = Depends(get_database)):
# result = await db.execute(select(Book).where(Book.author.like("曹%")))
# result = await db.execute(select(Book).where(Book.author.like("曹%") & (Book.price>100)))
# result = await db.execute(select(Book).where(Book.author.like("曹%") | (Book.price>100)))
# result = await db.execute(select(Book).where(~Book.author.like("曹%")))
# 需求:书籍id列表,数据库里面的id如果在 id列表里面,就返回
id_list = [1,2,3]
result = await db.execute(select(Book).where(Book.id.in_(id_list)))
book = result.scalars().all()
return book
聚合查询
聚合计算:func.方法(模型类.属性)
-
count:统计行数量
-
avg:求平均值
-
max:求最大值
-
min:求最小值
-
sum:求和
@app.get("/book/count")
async def get_count(db: AsyncSession = Depends(get_database)):
# 聚合查询 select(func.方法名(模型类.属性))
# result = await db.execute(select(func.count(Book.id)))
# result = await db.execute(select(func.max(Book.price)))
# result = await db.execute(select(func.sum(Book.price)))
result = await db.execute(select(func.avg(Book.price)))
count = result.scalar() # 用来提取一个数值->标量值
return count
分页查询
分页查询:select().offset().limit()
-
offset:跳过的记录数
-
limit:返回的记录数
@app.get("/book/get_books")
async def get_book_list(
page: int = 1, # 需要查询哪一页
page_size: int = 3, # 每页展示多少条数据
db: AsyncSession = Depends(get_database)
):
# 跳过的多少条数据
skip = (page-1) * page_size
# 查询【第 page 页】的那一页数据,offset 跳过的记录数;limit 每页的记录数
stmt = select(Book).offset(skip).limit(page_size)
result = await db.execute(stmt)
books = result.scalars().all()
return {"books": books}
查询总结
核心思路:
-
select() → db.execute() → 从 ORM 对象获取数据 → 响应结果
-
db.get(模型类, 主键值)
从 ORM 对象获取数据的方式
-
获取所有数据 scalars().all()
-
获取单条数据 scalars().first(): 提取第一个数据
scalar_one_or_none(): 提取一个或 null
scalar(): 提取标量值(配合聚合查询使用)
(2)新增
步骤:定义 ORM 对象 → 添加对象到事务:add(添加) → commit 提交到数据库
from pydantic import BaseModel # 导入基类
# 需求:用户输入图书信息(id、书名、作者、价格、出版社)->新增
# 用书输入->参数->请求体
# 定义需要新增的数据类
"""
BookBase(继承BaseModel)是 Pydantic 请求体模型,用来接收、校验前端传参,相当于 Java 里的 DTO。
Book(继承DeclarativeBase)是 SQLAlchemy ORM 模型,对应数据库表,相当于 Java 里的 Entity。
新增时,先把请求体数据转成 ORM 对象,再通过 ORM 方法写入数据库。
"""
class BookBase(BaseModel):
id: int
bookname: str
author: str
price: float
publisher: str
@app.post("/book/add_book")
async def add_book(book: BookBase, db: AsyncSession = Depends(get_database)):#获取数据库连接
# ORM对象->add->commit
# 获取 book 参数,创建图书对象(__dict__ 返回 book 对象的属性字典)
book_obj = Book(**book.__dict__) # 先把book通过__dict__转化为字典,然后**解包,对字典进行展开
db.add(book_obj)
await db.commit()
return book
(3)更新
步骤:查询 get → 属性重新赋值 → commit 提交到数据库
from pydantic import BaseModel # 导入基类
from fastapi import FastAPI,Depends,HTTPException
# 需求:修改图书信息:先查再改
# 设计思路:路径参数书籍id:作用是查找;请求体参数:作用是新数据(书名、作者、价格、出版社)
class BookUpdate(BaseModel):
# bookname: str
# author: str
# price: float
# publisher: str
#可选字段
bookname: Optional[str] = None
author: Optional[str] = None
price: Optional[float] = None
publisher: Optional[str] = None
@app.put("/book/update_book/{book_id}") # 通过主键id来找,同时也需要准备一个请求体参数:BookUpdate
async def update_book(book_id: int, data: BookUpdate, db: AsyncSession = Depends(get_database)):
# 1. 查询
book = await db.get(Book, book_id)
# 如果未找到,抛出异常
if book is None:
raise HTTPException(status_code=404, detail="Book not found")
# 2. 修改属性(重新赋值)
# book.bookname = data.bookname
# book.author = data.author
# book.price = data.price
# book.publisher = data.publisher
# 2. 只更新前端传了的字段(没传的不修改)
update_data = data.dict(exclude_unset=True) # 🔥只保留前端真正传过来的字段,忽略没传的字段
for key, value in update_data.items():
setattr(book, key, value)
# 3. 提交
await db.commit()
return book
(4)删除
步骤:查询 get → delete 删除 → commit 提交到数据库
@app.delete("/book/delete_book/{book_id}")
async def delete_book(book_id: int, db: AsyncSession = Depends(get_database)):
# 先查再删 提交
db_book = await db.get(Book, book_id)
# 如果查不到,抛出异常
if db_book is None:
raise HTTPException(status_code=404, detail="Book not found")
# 查到谁就删除谁
await db.delete(db_book)
await db.commit()
return {"message": "Book deleted"}
总结
安装sqlalchemy[asyncio]、aiomysql两个包,提供异步 ORM 与 MySQL 异步驱动。
建表
① create_async_engine 创建异步连接引擎
② 继承DeclarativeBase编写公共基类与业务表模型
③ run_sync(Base.metadata.create_all) 配合启动事件自动建表
操作数据
① Depends注入数据库会话
② 查询用select()、新增用add()、更新直接赋值、删除用delete(),操作后 commit 提交事务
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)