注:本文是python的学习笔记;不是教程!不是教程!内容可能有所疏漏,欢迎交流指正。
框架概述
什么是ORM?
ORM(Object-Relational Mapping,对象关系映射)是一种编程技术,用于在面向对象编程语言和关系数据库之间建立映射关系。它允许开发者使用面向对象的方式操作数据库,而不需要编写复杂的SQL语句。
ORM的核心优势:
- 代码可读性:使用Python对象而非SQL语句,代码更直观
- 数据库无关性:同一套代码可以在不同数据库间切换
- 安全性:自动防止SQL注入攻击
- 维护性:模型变更时自动处理数据库结构变化
SQLAlchemy
SQLAlchemy是Python生态系统中最成熟、功能最全面的ORM框架,自2006年发布以来一直是企业级应用的首选。它提供了完整的SQL工具包和对象关系映射系统。
核心优势:
- 成熟稳定:经过15+年生产环境验证,bug少,稳定性高
- 功能全面:支持复杂的数据库操作,包括原生SQL、存储过程、视图等
- 灵活架构:支持Core(表达式语言)和ORM两种使用模式
- 强大查询:提供类似SQL的查询构建器,支持复杂的联表查询
- 丰富生态:拥有大量第三方扩展,如Flask-SQLAlchemy、FastAPI-SQLAlchemy等
- 多数据库支持:支持PostgreSQL、MySQL、SQLite、Oracle、SQL Server等
适用场景:
- 企业级应用开发
- 复杂的数据库操作需求
- 需要高度定制化的项目
- 对稳定性要求极高的系统
Tortoise ORM
Tortoise ORM是受Django ORM启发的异步ORM框架,专为现代异步Python应用设计。它提供了简洁的API和出色的异步性能。
核心优势:
- 原生异步:所有操作都是异步的,性能优异,特别适合高并发场景
- 语法简洁:类似Django ORM的语法,学习曲线平缓
- 完美集成:与FastAPI、Starlette等异步框架无缝集成
- 自动化程度高:减少样板代码,开发效率高
- 现代化设计:基于Python 3.7+的现代特性设计
- 类型提示:完整的类型提示支持,IDE友好
适用场景:
- 现代异步Web应用(FastAPI、Starlette)
- 高并发API服务
- 微服务架构
- 对开发效率要求高的项目
核心特性对比
特性 | SQLAlchemy | Tortoise ORM | 详细说明 |
---|---|---|---|
同步/异步 | 同步为主,2.0版本支持异步 | 原生异步 | Tortoise在异步场景下性能更优 |
学习曲线 | 较陡峭,概念复杂 | 平缓,类似Django ORM | 新手更容易上手Tortoise |
性能 | 优秀,但同步操作有限制 | 异步操作性能卓越 | 高并发场景Tortoise优势明显 |
生态系统 | 非常丰富,插件众多 | 相对较新,生态在快速发展 | SQLAlchemy生态更成熟 |
文档质量 | 详尽完整,示例丰富 | 良好,但不如SQLAlchemy全面 | SQLAlchemy文档更完善 |
社区支持 | 庞大活跃,问题解决快 | 快速增长,响应积极 | SQLAlchemy社区更大 |
企业采用 | 广泛使用,大厂首选 | 新兴选择,增长迅速 | SQLAlchemy更适合企业级 |
数据库支持 | 全面支持主流数据库 | 支持主要数据库 | SQLAlchemy支持更全面 |
查询复杂度 | 支持极复杂查询 | 支持常见复杂查询 | SQLAlchemy在复杂查询上更强 |
代码风格 | 显式,配置灵活 | 隐式,约定优于配置 | 看个人和团队偏好 |
SQLAlchemy 与 Tortoise ORM
一:Tortoise ORM 特性
- 异步特性:Tortoise ORM 的所有数据库操作都是异步的,这意味着它们可以在单线程中同时处理多个数据库请求,而不会阻塞彼此。这大大提高了应用的并发性和性能。
- 模型定义:在Tortoise ORM 中,开发者使用Python类来定义数据库表结构。这些类中的属性对应于数据库表中的列,这使得数据库操作更加直观和易于理解。
- 查询构建:Tortoise ORM 提供了强大的查询构建功能,允许开发者构建复杂的查询条件,以检索所需的数据。这包括使用链式调用、比较运算符、逻辑运算符等。
- 关系映射:Tortoise ORM 支持定义模型之间的关系,如一对一、一对多、多对多等。这使得开发者可以轻松地处理复杂的数据关系,并在代码中以直观的方式表示它们。
- 迁移和同步:Tortoise ORM 还提供了数据库迁移工具,用于管理数据库模式的变更。这使得在应用开发过程中,可以轻松地添加、修改或删除表结构,而无需手动编写SQL语句。
二:模型定义
1. SQLAlchemy ORM
SQLAlchemy 是 Python 中最成熟的 ORM 框架,但其语法与 Django ORM 有较大差异。在 FastAPI 中,通常使用 SQLAlchemy 的声明式方式定义模型。
SQLAlchemy使用声明式方式定义模型,需要显式定义所有关系
# models.py# 需要先导入所有需要使用的字段类型
from sqlalchemy import Column, Integer, String, ForeignKey, Numericfrom sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationshipBase = declarative_base()# 主表 (模型B)
class Category(Base):__tablename__ = "categories" # 数据库表名称id = Column(Integer, primary_key=True)name = Column(String(50))# 必须显式定义反向关系products = relationship("Product", back_populates="category")# 从表 (模型A)
class Product(Base):__tablename__ = "products"id = Column(Integer, primary_key=True)name = Column(String(100))price = Column(Numeric(10, 2))# 第一步:定义外键约束, 这是数据库级别的外键约束category_id = Column(Integer, ForeignKey("categories.id"))# 第二步:定义反向关系, 这是 Python 对象级别的关系category = relationship("Category", back_populates="products")# 在 SQLAlchemy 中,定义关系需要两步:
1. 在从表定义外键约束
2. 在两侧都定义 relationship 来创建对象间的关联# 使用方式:
通过 product.category 访问产品所属类别
通过 category.products 访问类别下所有产品
2. Tortoise ORM
Tortoise ORM 语法与 Django ORM 非常相似,更加简洁直观自动处理反向关系。
CharField 字符串类型字段
- max_length:字符串的最大长度。
- default:字段的默认值。
- null:是否允许字段为NULL。默认为False。
- unique:字段值是否必须在数据库中唯一。默认为False。
- index:是否为该字段创建索引。默认为False。
- description:字段的描述信息,主要用于文档和生成的SQL schema。
- pk:是否将此字段设置为主键。默认为False。
- generated:是否为自动生成的字段(如自增主键)。默认为False。
FloatField 浮点类型字段
- default, null, unique, index, description, pk, generated: 与CharField相同。
- gt, lt, ge, le:用于设置字段值的范围限制(大于、小于、大于等于、小于等于)。
IntegerField 整数类型字段
- default, null, unique, index, description, pk, generated:与CharField相同。
- gt, lt, ge, le:用于设置字段值的范围限制(大于、小于、大于等于、小于等于)。
BooleanField 布尔类型字段
- default, null, description:与CharField相同。
DateField 和 DateTimeField 日期时间类型字段
- auto_now:如果设置为True,则在对象保存时自动设置为当前日期/时间。默认为False。
- auto_now_add:如果设置为True,则在对象第一次保存时自动设置为当前日期/时间。默认为False。
- default, null, unique, index, description, pk:与CharField相同。
ForeignKeyField 关系型字段
- to (str or Type[Model]):指定外键关联的模型。
- related_name (str):在关联模型上创建反向关系的名称。
- on_delete (str):当关联的对象被删除时的行为(如CASCADE、SET_NULL等)。
- default, null, description, pk, index:与CharField相同。
ManyToManyField 关系型字段
- through:用于定义多对多关系的中间表。如果不指定,Tortoise ORM将自动创建一个中间表。
- related_name:与ForeignKeyField中的用法相同,用于反向查询。
- default, null, description, pk, index:与CharField相同。
TextField文本类型字段
- default, null, description:与CharField相同。通常用于存储大量文本。
JSONField序列话类型字段
- default, null, description:与CharField相同。用于存储JSON格式的数据。
# models.py
from tortoise import fields, models
from tortoise.contrib.pydantic import pydantic_model_creator# 主表 (模型B)
class Category(models.Model):id = fields.IntField(pk=True)name = fields.CharField(max_length=50)# 不需要显式定义反向关系,会自动创建 products 属性# products = fields.ReverseRelation["Product"] # 这行可以不写,会自动生成# 从表 (模型A)
class Product(models.Model):id = fields.IntField(pk=True)name = fields.CharField(max_length=100)price = fields.DecimalField(max_digits=10, decimal_places=2)# 只需在这一侧定义外键category = fields.ForeignKeyField("models.Category", related_name="products", on_delete=fields.CASCADE)# 使用方式:
通过 product.category 访问产品所属类别
通过 category.products 访问类别下所有产品 # 通过外键的related_name="products" 的名字来访问
模型定义对比总结:
方面 | SQLAlchemy | Tortoise ORM |
---|---|---|
语法复杂度 | 较复杂,需要显式定义关系 | 简洁,自动处理反向关系 |
字段定义 | 需要导入具体字段类型 | 统一的fields模块 |
关系定义 | 双向显式定义 | 单向定义,自动生成反向 |
元数据配置 | 使用__tablename__ | 使用Meta类 |
字段参数详解
通用参数:
null
: 是否允许为空(默认False)default
: 默认值unique
: 是否唯一(默认False)index
: 是否创建索引(默认False)description
: 字段描述
CharField特有参数:
max_length
: 最大长度(必需)
数值字段特有参数:
ge
: 大于等于gt
: 大于le
: 小于等于lt
: 小于
时间字段特有参数:
auto_now
: 每次保存时自动更新auto_now_add
: 创建时自动设置
外键字段特有参数:
related_name
: 反向关系名称on_delete
: 删除策略(CASCADE、SET_NULL、RESTRICT等)
三:数据库迁移
1. SQLAlchemy ORM迁移(使用 Alembic)
SQLAlchemy 本身不提供迁移工具,通常与 Alembic 配合使用。
1:安装 Alembic 库
pip install alembic
2:初始化 Alembic
alembic init alembic# 这会创建以下文件结构:
# alembic/
# ├── versions/ # 迁移文件目录
# ├── env.py # 环境配置
# ├── script.py.mako # 迁移脚本模板
# └── alembic.ini # 配置文件
3:配置 alembic.ini 文件
sqlalchemy.url = sqlite:///./app.db
4:配置env.py文件
2. Tortoise ORM迁移(使用 aerich)
Tortoise ORM 使用 aerich 进行迁移。
1:安装 aerich 库
pip install aerich
2:在 FastAPI 应用中配置数据库
# main.py
from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoiseapp = FastAPI()TORTOISE_ORM = {"connections": {"default": "postgresql://user:password@localhost/dbname"},"apps": {"models": {"models": ["app.models", "aerich.models"],"default_connection": "default",},},
}register_tortoise(app,config=TORTOISE_ORM,generate_schemas=False, # 不自动生成表,使用迁移add_exception_handlers=True,
)
3:初始化配置(整个项目只需执行一次)
aerich init -t app.main.TORTOISE_ORM # TORTOISE_ORM配置的位置# 初始化完成会在 当前目录下 生成一个文件(pyproject.toml) 和文件夹(migrations)
# pyproject.toml:保存配置文件路径,低版本可能是aerich.ini
# migrations:存放迁移文件的目录
4:模型迁移(在数据库创建对应模型的数据表)
aerich init-db# 此时数据库中就会有对应数据模型的数据表
# 如果TORTOISE_ORM配置文件中的models改了名字,则执行这条命令时需要增加**–app**参数,来指定修改的名称
5:更改模型字段后重新生成迁移文件
aerich migrate [--name '迁移记录'] # 标记修改操作记录(可选)
6:数据迁移(更新数据库的表字段)
aerich upgrade # 基于第五步操作
7:回滚迁移
aerich downgrade # 默认回退上一步的版本
8:查看历史迁移记录
aerich history
四:数据库操作(CURD)
1. SQLAlchemy CURD 操作
from sqlalchemy.orm import Session
from . import models, schemas# 创建 (Create)
def create_user(db: Session, user: schemas.UserCreate):"""创建用户"""# 方法1:使用构造函数db_user = models.User(username=user.username,email=user.email,hashed_password="hashed_password")db.add(db_user) # 添加到会话db.commit() # 提交事务db.refresh(db_user) # 刷新对象属性return db_user# 读取 (Read)
def get_user(db: Session, user_id: int):return db.query(models.User).filter(models.User.id == user_id).first()def get_users(db: Session, skip: int = 0, limit: int = 100):return db.query(models.User).offset(skip).limit(limit).all()def get_users_by_filter(db: Session):# 复杂查询示例return (db.query(models.User).filter(models.User.is_active == True).order_by(models.User.username).all())# 更新 (Update)
def update_user(db: Session, user_id: int, user_data: schemas.UserUpdate):db_user = db.query(models.User).filter(models.User.id == user_id).first()if db_user:# 更新模型属性for key, value in user_data.dict(exclude_unset=True).items():setattr(db_user, key, value)db.commit()db.refresh(db_user)return db_user# 删除 (Delete)
def delete_user(db: Session, user_id: int):db_user = db.query(models.User).filter(models.User.id == user_id).first()if db_user:db.delete(db_user)db.commit()return db_user# 关系查询
def get_user_posts(db: Session, user_id: int):db_user = db.query(models.User).filter(models.User.id == user_id).first()if db_user:return db_user.posts # 使用关系属性return []# 多对多关系
def add_tag_to_post(db: Session, post_id: int, tag_id: int):post = db.query(models.Post).filter(models.Post.id == post_id).first()tag = db.query(models.Tag).filter(models.Tag.id == tag_id).first()if post and tag:post.tags.append(tag)db.commit()
2. Tortoise ORM CURD 操作
from tortoise.transactions import in_transaction
from . import models, schemas# 创建 (Create)
async def create_user(user: schemas.UserCreate):return await models.User.create(username=user.username,email=user.email,hashed_password="hashed_password")# 读取 (Read)
async def get_user(user_id: int):'''get() 方法用于根据主键获取单条数据。如果数据不存在,则抛出错误get_or_none() 方法用于根据主键获取单条数据。如果数据不存在,将返回 None'''return await models.User.get(id=user_id)return await models.User.get_or_none(id=user_id)async def get_user(user_id: int):'''filter() 方法用于根据条件查询数据,返回满足条件的数据集(QuerySet对象)。使用 first() 方法获取第一个结果;使用 all() 方法获取所有的查询结果。'''return await models.User.filter(id=user_id).first()return await models.User.filter(id=user_id).all()async def get_users(skip: int = 0, limit: int = 100):'''all() 方法用于查询所有数据,返回所有数据集(QuerySet对象)。如果不加任何条件,它会返回表中的所有记录。'''return await models.User.all().offset(skip).limit(limit)async def get_users_by_filter():# 复杂查询示例return await (models.User.filter(is_active=True).order_by('username'))# 更新 (Update)
async def update_user(user_id: int, user_data: schemas.UserUpdate):# 方法 1:先查询再更新user = await models.User.filter(id=user_id).first()if user:user_data_dict = user_data.dict(exclude_unset=True)for key, value in user_data_dict.items():setattr(user, key, value)await user.save()return user# 方法 2:直接使用 updateupdated = await models.User.filter(id=user_id).update(**user_data.dict(exclude_unset=True))if updated:return await models.User.filter(id=user_id).first()return None# 删除 (Delete)
async def delete_user(user_id: int):user = await models.User.filter(id=user_id).first()if user:await user.delete()return userreturn None# 关系查询
async def get_user_posts(user_id: int):# 方法 1:通过用户查询user = await models.User.filter(id=user_id).first().prefetch_related('posts')if user:return user.posts# 方法 2:直接查询文章return await models.Post.filter(author_id=user_id)# 多对多关系
async def add_tag_to_post(post_id: int, tag_id: int):post = await models.Post.filter(id=post_id).first()tag = await models.Tag.filter(id=tag_id).first()if post and tag:await post.tags.add(tag) # 添加多对多关系return Truereturn False# 事务处理
async def create_post_with_tags(post_data, tag_ids):async with in_transaction() as conn:# 在事务中创建文章post = await models.Post.create(title=post_data.title,content=post_data.content,author_id=post_data.author_id,using_db=conn)# 添加标签for tag_id in tag_ids:tag = await models.Tag.filter(id=tag_id).using_db(conn).first()if tag:await post.tags.add(tag, using_db=conn)return post
2. 比较运算符
async def get_users():'''获取(= 等于)1的数据'''return await models.User.filter(id=1).all()async def get_users():'''获取(__not 不等于)1的数据'''return await models.User.filter(id__not=1).all()async def get_users():'''获取(__gt 大于)1的数据'''return await models.User.filter(id__gt=1).all()async def get_users():'''获取(id__gte 大于等于)1的数据'''return await models.User.filter(id__gte=1).all()async def get_users():'''获取(id__lt 小于)1的数据'''return await models.User.filter(id__lt=1).all()async def get_users():'''获取(id__lte 小于等于)1的数据'''return await models.User.filter(id__lte=1).all()
3. 成员运算符
async def get_users():'''获取(__in 在)列表中的数据'''names = ['张三', '李四', '王五']return await models.User.filter(user__in=names).all()
4. 模糊查询
# Tortoise ORM 不直接支持SQL中的LIKE模糊查询,async def get_users():'''获取(__icontains 包含)200的数据'''return await models.User.filter(num__icontains='200').all()async def get_users():'''获取(__istartswith 开头)200的数据'''return await models.User.filter(num__istartswith='200').all()async def get_users():'''获取(__iendswith 结尾)200的数据'''return await models.User.filter(num__iendswith='200').all()async def get_users():'''获取(__range 指定范围之间)的数据'''# 获取学号在[2021, 2024]之间的数据return await models.User.filter(num__range=[2021, 2024]).all()async def get_users():'''获取(__isnull 是否为空)的数据'''return await models.User.filter(num__isnull=True).all()
5. exclude():排除
用于排除满足条件的数据,返回不满足条件的数据集
async def get_users(name : str = '张三'):'''(exclude 排除)名字不是 '张三' 的所有数据'''return await models.User.exclude(user=name).all()
6. count():统计
用于统计满足条件的数据数量
async def get_users(num: int = 18):'''(count 统计)(__gt 大于)18的数据量'''return await models.User.filter(age__gt=num).count()
7. order_by():排序
用于安装指定字段排序查询结果
async def get_users():'''按id进行排序, 获取所有数据'''return await models.User.all().orderby('id') # 升序return await models.User.all().orderby('id') # 降序
原生SQL查询
SQLAlchemy 原生SQL
from sqlalchemy import textdef execute_raw_sql(db: Session):"""执行原生SQL"""result = db.execute(text("""SELECT u.username, COUNT(p.id) as post_countFROM users uLEFT JOIN posts p ON u.id = p.author_idGROUP BY u.id, u.usernameORDER BY post_count DESC"""))return result.fetchall()def execute_raw_sql_with_params(db: Session, min_posts: int):"""带参数的原生SQL"""result = db.execute(text("""SELECT u.username, COUNT(p.id) as post_countFROM users uLEFT JOIN posts p ON u.id = p.author_idGROUP BY u.id, u.usernameHAVING COUNT(p.id) >= :min_postsORDER BY post_count DESC"""), {"min_posts": min_posts})return result.fetchall()
Tortoise ORM 原生SQL
from tortoise import connectionsasync def execute_raw_sql():"""执行原生SQL"""conn = connections.get("default")result = await conn.execute_query("""SELECT u.username, COUNT(p.id) as post_countFROM users uLEFT JOIN posts p ON u.id = p.author_idGROUP BY u.id, u.usernameORDER BY post_count DESC""")return resultasync def execute_raw_sql_with_params(min_posts: int):"""带参数的原生SQL"""conn = connections.get("default")result = await conn.execute_query("""SELECT u.username, COUNT(p.id) as post_countFROM users uLEFT JOIN posts p ON u.id = p.author_idGROUP BY u.id, u.usernameHAVING COUNT(p.id) >= $1ORDER BY post_count DESC""", [min_posts])return result
选择建议
何时选择SQLAlchemy
适合场景:
- 企业级应用:需要高度稳定性和成熟的生态系统
- 复杂业务逻辑:涉及复杂的数据库操作和查询
- 团队经验:团队对SQLAlchemy有丰富经验
- 数据库多样性:需要支持多种数据库类型
- 现有项目:已有基于SQLAlchemy的项目需要维护
优势:
- 生态系统成熟,第三方工具丰富
- 文档详尽,社区支持强大
- 功能全面,支持复杂的数据库操作
- 经过长期生产环境验证
何时选择Tortoise ORM
适合场景:
- 现代异步应用:基于FastAPI、Starlette等异步框架
- 高并发需求:需要处理大量并发请求
- 快速开发:追求开发效率和代码简洁性
- 新项目:从零开始的新项目
- 微服务架构:轻量级的微服务应用
优势:
- 原生异步支持,性能优异
- 语法简洁,学习成本低
- 与现代异步框架集成度高
- 开发效率高,代码量少