Tortoise-ORM 单表 CRUD 操作
Tortoise-ORM 的单表 CRUD 操作(Create-创建、Read-查询、Update-更新、Delete-删除),以 Student(学生)模型为实战案例,提供完整可运行的模型定义和 CRUD 函数,包含异常处理、日志输出和细节说明,适配 Tortoise-ORM 0.25.0 版本。
一、前置准备:Student 模型定义
首先定义 Student模型,对应数据库中的 students 表,包含学生核心信息(ID、姓名、年龄、邮箱),字段添加约束和描述,便于后续 CRUD 操作和维护。
from tortoise import fields, models
class Student(models.Model):
"""
学生模型,用于演示 Tortoise-ORM 单表 CRUD 操作
对应数据库中的 students 表,包含学生基本信息
"""
# 主键字段:学生ID,自增整数,必填,作为唯一标识
id = fields.IntField(pk=True, description="学生ID,主键(自增)")
# 学生姓名:字符串,最大长度50,必填(默认不允许为空)
name = fields.CharField(max_length=50, description="学生姓名(必填)")
# 学生年龄:整数,可选(允许为空)
age = fields.IntField(null=True, description="学生年龄(可选,可为空)")
# 学生邮箱:字符串,最大长度100,唯一约束,可选(允许为空)
email = fields.CharField(
max_length=100,
unique=True,
null=True,
description="学生邮箱(可选,需唯一,可为空)"
)
# 模型元数据配置
class Meta:
table = "students" # 自定义数据库表名,固定为 students
# 自定义字符串表示:打印 Student 对象时,显示核心信息(便于调试)
def __str__(self):
return f"Student: {self.name}, Age: {self.age}, Email: {self.email}"
模型关键说明
- 模型必须继承
tortoise.models.Model,是 Tortoise-ORM 识别模型的核心; pk=True标记主键,IntField(pk=True)自动设为自增整数,无需手动赋值;unique=True给邮箱添加唯一约束,确保所有学生的邮箱不重复,重复插入会抛出异常;null=True表示字段可选,创建学生时可不用传入 age 和 email,数据库中对应字段为 NULL;__str__方法:自定义对象的字符串输出格式,调试时打印学生对象可快速查看核心信息。
二、CRUD 操作完整实现
以下是针对 Student 模型的完整 CRUD 函数,每个函数对应一种操作,包含参数说明、返回值、异常捕获和日志输出,确保操作安全、可追溯。所有函数均为异步函数(async def),需结合 await 调用(适配 Tortoise-ORM 异步特性)。
2.1 导入依赖与日志配置
先导入所需模块、模型和异常类,配置日志(便于查看操作记录和错误信息):
from tortoise import Tortoise
from tortoise.exceptions import DoesNotExist, IntegrityError
from models import Student # 导入上面定义的 Student 模型(路径根据实际项目调整)
import logging
# 配置日志:设置日志级别为 INFO,输出操作记录和错误信息
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__) # 创建日志对象,用于输出CRUD操作日志
2.2创建操作(Create):create_student
功能:创建一条学生记录,支持可选字段(age、email),自动校验邮箱唯一性,捕获创建过程中的异常(如邮箱重复)。
async def create_student(name: str, age: int = None, email: str = None) -> Student:
"""
创建学生记录(核心:新增数据)
Args:
name: 学生姓名,必填(字符串,最大长度50)
age: 学生年龄,可选(整数,可为空,无默认值)
email: 学生邮箱,可选(字符串,最大长度100,需唯一,可为空)
Returns:
Student: 创建成功后的学生对象(包含所有字段信息,如自动生成的id)
Raises:
IntegrityError: 邮箱重复(违反unique约束)或其他数据库完整性错误
Exception: 未知错误(如字段格式错误)
"""
try:
# 调用 Student.create() 异步创建学生记录,返回创建后的对象
student = await Student.create(name=name, age=age, email=email)
# 输出日志:记录创建成功的学生信息
logger.info(f"创建学生成功 | 姓名: {student.name}, 年龄: {student.age}, 邮箱: {student.email}, ID: {student.id}")
return student
except IntegrityError as e:
# 捕获邮箱重复等完整性约束错误
logger.error(f"创建学生失败 | 原因: {str(e)}(大概率是邮箱重复,请更换邮箱)")
raise # 重新抛出异常,让调用者处理(如返回错误响应)
except Exception as e:
# 捕获其他未知错误
logger.error(f"创建学生时发生未知错误 | 原因: {str(e)}")
raise
2.3查询操作(Read):4个常用查询函数
提供4种查询场景,覆盖「精确查询、模糊查询、全量查询」,满足日常开发中大部分查询需求,均捕获「学生不存在」等异常。
(1)get_student:根据ID精确查询单个学生
async def get_student(student_id: int) -> Student:
"""
根据学生ID精确查询单个学生(核心:单条数据查询)
Args:
student_id: 学生ID(主键,整数)
Returns:
Student: 查询到的学生对象
Raises:
DoesNotExist: 学生ID不存在,查询不到数据
Exception: 未知查询错误
"""
try:
# 调用 Student.get() 异步查询:根据id精确匹配(主键查询效率最高)
student = await Student.get(id=student_id)
logger.info(f"查询学生成功 | ID: {student.id}, 姓名: {student.name}")
return student
except DoesNotExist:
# 捕获「学生不存在」错误
logger.error(f"查询学生失败 | 原因: 学生ID {student_id} 不存在")
raise
except Exception as e:
logger.error(f"查询学生时发生未知错误 | 原因: {str(e)}")
raise
(2)get_students_by_name:根据姓名模糊查询学生列表
async def get_students_by_name(name: str) -> list[Student]:
"""
根据姓名模糊查询学生列表(核心:模糊查询,支持部分匹配)
Args:
name: 学生姓名(字符串,支持部分匹配,如传入"张",查询所有姓张的学生)
Returns:
list[Student]: 匹配成功的学生对象列表(无匹配时返回空列表)
Raises:
Exception: 未知查询错误
"""
try:
# 调用 Student.filter() 异步过滤:name__contains 表示「包含」(模糊匹配)
# 其他模糊匹配方式:name__startswith(以...开头)、name__endswith(以...结尾)
students = await Student.filter(name__contains=name)
logger.info(f"模糊查询学生成功 | 姓名包含: '{name}', 共查询到 {len(students)} 名学生")
return students
except Exception as e:
logger.error(f"模糊查询学生时发生错误 | 原因: {str(e)}")
raise
(3)get_all_students:查询所有学生列表
async def get_all_students() -> list[Student]:
"""
查询所有学生列表(核心:全量查询)
Returns:
list[Student]: 所有学生对象列表(无学生时返回空列表)
Raises:
Exception: 未知查询错误
"""
try:
# 调用 Student.all() 异步查询所有学生,返回列表
students = await Student.all()
logger.info(f"查询所有学生成功 | 共查询到 {len(students)} 名学生")
return students
except Exception as e:
logger.error(f"查询所有学生时发生错误 | 原因: {str(e)}")
raise
2.4更新操作(Update):update_student
功能:根据学生ID更新学生信息,支持「部分字段更新」(无需传入所有字段),允许将age、email设为NULL,校验邮箱唯一性。
async def update_student(student_id: int, name: str = None, age: int = None, email: str = None) -> Student:
"""
更新学生信息(核心:部分字段更新,不改变未传入的字段)
Args:
student_id: 学生ID(主键,必须传入,指定要更新的学生)
name: 新姓名,可选(不传入则不更新姓名)
age: 新年龄,可选(传入None则将年龄设为NULL,不传入则不更新)
email: 新邮箱,可选(传入None则将邮箱设为NULL,不传入则不更新,需唯一)
Returns:
Student: 更新后的学生对象
Raises:
DoesNotExist: 学生ID不存在
IntegrityError: 邮箱重复(违反unique约束)
Exception: 未知更新错误
"""
try:
# 1. 先根据ID查询到要更新的学生对象(必须先查询,再修改)
student = await Student.get(id=student_id)
# 2. 按需更新字段:只更新传入的非None字段
if name: # 姓名不为空则更新
student.name = name
if age is not None: # 允许将年龄设为NULL(传入None时执行更新)
student.age = age
if email is not None: # 允许将邮箱设为NULL(传入None时执行更新)
student.email = email
# 3. 调用 save() 异步保存更新(仅更新修改过的字段,效率高)
await student.save()
logger.info(f"更新学生成功 | ID: {student.id}, 新信息: 姓名={student.name}, 年龄={student.age}, 邮箱={student.email}")
return student
except DoesNotExist:
logger.error(f"更新学生失败 | 原因: 学生ID {student_id} 不存在")
raise
except IntegrityError as e:
logger.error(f"更新学生失败 | 原因: {str(e)}(大概率是邮箱重复,请更换邮箱)")
raise
except Exception as e:
logger.error(f"更新学生时发生未知错误 | 原因: {str(e)}")
raise
2.5删除操作(Delete):delete_student
功能:根据学生ID删除学生记录,先校验学生是否存在,避免无效删除操作,删除后输出日志。
async def delete_student(student_id: int) -> None:
"""
删除学生记录(核心:删除单条数据)
Args:
student_id: 学生ID(主键,指定要删除的学生)
Returns:
None: 无返回值(删除成功即完成)
Raises:
DoesNotExist: 学生ID不存在,无法删除
Exception: 未知删除错误
"""
try:
# 1. 先根据ID查询到要删除的学生对象(校验学生是否存在)
student = await Student.get(id=student_id)
# 2. 调用 delete() 异步删除学生记录
await student.delete()
logger.info(f"删除学生成功 | ID: {student.id}, 姓名: {student.name}")
except DoesNotExist:
logger.error(f"删除学生失败 | 原因: 学生ID {student_id} 不存在,无需删除")
raise
except Exception as e:
logger.error(f"删除学生时发生未知错误 | 原因: {str(e)}")
raise
三、CRUD 操作调用示例
所有 CRUD 函数均为异步函数,需在「异步环境」中调用(如结合 FastAPI 接口、asyncio 主函数)。以下是完整的调用示例,可直接复制运行测试(需先初始化 Tortoise-ORM):
import asyncio
# Tortoise-ORM 初始化配置(需与项目配置一致)
TORTOISE_ORM = {
"connections": {
"default": "sqlite://db.sqlite3" # 开发环境使用SQLite,无需启动数据库服务
},
"apps": {
"models": {
"models": ["models", "aerich.models"], # 导入Student模型和Aerich迁移模型
"default_connection": "default",
}
}
}
async def main():
"""测试CRUD操作的主函数(异步)"""
# 1. 初始化 Tortoise-ORM
await Tortoise.init(config=TORTOISE_ORM)
# 2. 生成数据库表(开发环境,仅首次运行需执行)
await Tortoise.generate_schemas()
try:
# ------------------- 测试创建学生 -------------------
student1 = await create_student(
name="张三",
age=18,
email="zhangsan@test.com"
)
# ------------------- 测试查询操作 -------------------
# 按ID查询
get_stu = await get_student(student1.id)
print("按ID查询结果:", get_stu)
# 模糊查询(查询姓张的学生)
like_stu = await get_students_by_name("张")
print("模糊查询结果:", [str(stu) for stu in like_stu])
# 查询所有学生
all_stu = await get_all_students()
print("所有学生:", [str(stu) for stu in all_stu])
# ------------------- 测试更新学生 -------------------
update_stu = await update_student(
student_id=student1.id,
age=19, # 只更新年龄,其他字段不变
email="zhangsan_update@test.com" # 更新邮箱
)
print("更新后学生:", update_stu)
# ------------------- 测试删除学生 -------------------
await delete_student(student_id=student1.id)
print("删除学生成功")
except Exception as e:
print(f"测试失败: {str(e)}")
finally:
# 关闭 Tortoise-ORM 连接
await Tortoise.close_connections()
# 运行异步主函数
if __name__ == "__main__":
asyncio.run(main())
四、关键注意事项(避坑重点)
- 异步调用:所有 CRUD 函数和 Tortoise-ORM 方法(如
create、get、save)均为异步,必须用await调用,且需在异步函数/异步环境中执行(如async def函数、FastAPI 接口)。 - 异常处理:函数中已捕获常见异常(
DoesNotExist、IntegrityError),但调用时仍需捕获异常(如在 FastAPI 接口中返回错误响应),避免程序崩溃。 - 邮箱唯一性:email 字段有
unique=True约束,创建/更新时传入重复邮箱会抛出IntegrityError,需提前校验或捕获异常处理。 - 部分更新:
update_student函数中,age is not None和email is not None是关键——若需将字段设为 NULL,传入age=None即可;若不更新该字段,不传入参数即可。 - 数据库初始化:测试前需初始化 Tortoise-ORM,并生成
students表(可通过generate_schemas=True或 Aerich 迁移工具)。 - 生产环境:生产环境中,禁止使用
generate_schemas=True,需通过 Aerich 迁移工具管理表结构;同时需加强异常捕获,返回友好的错误提示。
五、常见问题排查
- 问题1:调用函数时报「RuntimeWarning: coroutine ... was never awaited」:忘记给异步函数加
await,需在调用时写await create_student(...)。 - 问题2:创建/更新学生时报「IntegrityError」:大概率是邮箱重复,检查邮箱是否已存在,或更换邮箱值。
- 问题3:查询/更新/删除时报「DoesNotExist」:传入的
student_id不存在,检查 ID 是否正确,或先通过get_all_students查看所有学生 ID。 - 问题4:提示「No module named 'models'」:
from models import Student路径错误,根据实际项目结构调整(如from app.models import Student)。 - 问题5:无法生成 students 表:确保 Tortoise 初始化时,模型所在模块已添加到
apps.models列表中。

浙公网安备 33010602011771号