SQLAlchemy2.0 使用手册 - (二) ORM层篇

SQLAlchemy2.0 使用手册 - (二) ORM层篇

ORM 的概念

ORM 的概念:

  • 定义:ORM(Object-Relational Mapping)是一种编程技术,用于将数据库表结构映射到面向对象编程中的类和对象。在 SQLAlchemy 中,ORM 允许开发者通过操作 Python 对象来间接操作数据库,而无需直接编写 SQL 语句。
  • 优势
    • 提高开发效率:通过对象化操作简化数据库交互。
    • 增强代码可读性:将数据库操作抽象为对象操作,更符合面向对象编程的思维方式。
    • 解耦数据库逻辑:减少 SQL 语句的直接使用,便于维护和扩展。
    • 支持事务和会话管理:ORM 提供了更高级的事务管理和会话控制机制。

ORM层

创建会话 Session

Session 不是线程安全的。但是一般情况下,web 框架应该在每个请求开始时获取一个 session, 所以也不是问题。

python 复制代码
from sqlalchemy import create_engine
from sqlalchemy.orm import Session

engine = create_engine('mysql+pymysql://user:password@localhost/foo?charset=utf8mb4')

with Session(engine) as session:
    session.add(foo)
    session.commit()

# 推荐使用 sessionmaker 来创建一个会话工厂,这样就不用每次都输入 engine 参数了
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(engine)

# 使用上下文管理器自动关闭会话
with Session() as session:
    ...

声明 ORM 模型类

  • 使用 __tablename__ 指定数据库表名
  • 使用 Column 声明每个字段
  • 使用 Integer / String ... 指定字段类型
  • 使用 index 参数指定索引
  • 使用 unique 参数指定唯一索引
  • 使用 __table_args__ 指定其他属性,比如联合索引
python 复制代码
from datetime import datetime
from sqlalchemy import Integer, Column, String, func, UniqueConstraint
from sqlalchemy.orm import DeclarativeBase, declarative_base, relationship

class Base(DeclarativeBase):
    """
    Base(DeclarativeBase) : 声明式基类 (SQLAlchemy 2.0+ 风格)
    Base = declarative_base()  # declarative_base()方法生成一个ORM基类
    """
    @declared_attr.directive
    def __tablename__(cls) -> str:
        return cls.__name__.lower()  # 表名小写

# 定义模型类
class User(Base):
    __tablename__ = "users"
    __table_args__ = (
        Index('id', 'name'),  # 联合索引
        UniqueConstraint("name", "time_created")  # 联合唯一索引
    )  
    id = Column(Integer, primary_key=True)
    name = Column(String(30), index=True)
    fullname = Column(String, unique=True)
    # 对于特别大的字段,还可以使用 deferred,这样默认不加载这个字段
    description = deferred(Column(Text))
    # 默认值,注意传递的是函数,不是现在的时间
    time_created = Column(DateTime(Timezone=True), default=datetime.now)
    # 或者使用服务器默认值,但是必须在表创建的时候就设置好,会成为表的 schema 的一部分
    time_created = Column(DateTime(timezone=True), server_default=func.now())
    time_updated = Column(DateTime(timezone=True), onupdate=func.now())

class CarReview(Base):
    __tablename__ = "car_review"
    id = Column(Integer, primary_key=True)
    label_workflow = Column(String(50))
    opinion = Column(String(200))
    
    def __repr__(self):
        return f"CarReview(id={self.id}, status={self.label_workflow})"


# 调用 create_all 创建所有模型,把所有集成Base类的类,创建表结构
Base.metadata.create_all(engine)

# 把所有集成Base类的类,删除表
Base.metadata.drop_all(engine)

# 如果只需要创建一个模型
User.__table__.create(engine)

ORM 层 CRUD

需要注意SQLAlchemy 2.0的变化,和 1.x API 不同,2.0 API 中不再使用 query(),而更推荐使用 session.execute() 配合 select() 。

Insert(插入数据)
python 复制代码
# 创建对象实例
new_review = CarReview(label_workflow="pending", opinion="Initial review")
# 插入单条数据
with Session() as session:
    session.add(new_review)  # 将对象添加到会话
    session.commit()         # 提交事务
    print("Inserted ID:", new_review.id)  # 如果要获取插入后的ID,当然也可以commit之后再读(自增ID自动填充)
 
# 批量插入数据
reviews = [
    CarReview(label_workflow="approved", opinion="Good"),
    CarReview(label_workflow="rejected", opinion="Poor")
]
with Session() as session:
    session.add_all(reviews)  # 批量添加
    session.commit()
    
# 批量插入数据-优化 
# 使用 session.bulk_save_objects(...) 直接插入多个对象
s = Session()
objects = [
    User(name="u1"),
    User(name="u2"),
    User(name="u3")
]
s.bulk_save_objects(objects)
s.commit()

# 使用 bulk_insert_mappings 可以省去创建对象的开销,直接插入字典
users = [
    {"name": "u1"},
    {"name": "u2"},
    {"name": "u3"},
]
s.bulk_insert_mappings(User, users)
s.commit()

# 使用 bulk_update_mappings 可以批量更新对象,字典中的 主键id 会被用作 where 条件,
# 其他字段全部用于更新
session.bulk_update_mappings(User, users)
Query(查询数据)
python 复制代码
# 查询全部数据
with Session() as session:
    stmt = select(CarReview)  # 等效 SELECT * FROM car_review
    results = session.execute(stmt)  # 执行查询
    reviews = results.scalars().all()  # 转为映射模型对象列表
    # reviews = session.scalars(stmt).all()  # 或 返回映射模型对象列表
    for review in reviews:
        print(review.label_workflow, review.opinion)
       
# 查询指定字段数据
with Session() as session:
    stmt = select(CarReview.new_label_opinion, CarReview.label_workflow)
    results = session.execute(stmt)  # 返回 Row 对象
    
    stmt = select(CarReview).options(load_only(CarReview.new_label_opinion, CarReview.label_workflow))
    reviews = session.execute(stmt).scalars().all()  # 返回映射模型对象列表(对象引用未装载的字段时会导致延迟查询,不推荐使用)
    
    sql = text("select id, label_workflow from car_reviews WHERE id != :id")
    stmt = select(CarReview).from_statement(sql).params(id=1)
    results = session.execute(stmt).scalars().all()  # 返回映射模型对象列表(对象引用未装载的字段时会导致延迟查询,不推荐使用)

# 条件查询(WHERE)
# where 的参数是 `==` 构成的表达式;order_by 还可以使用 User.id.desc() 表示逆序排列
stmt = select(User).where(User.name == "john").order_by(User.id)  
results = session.execute(stmt)
# 一般情况下,当选取整个对象的时候,都要用 scalars 方法,否则返回的是一个包含一个对象的 tuple
for user in results.scalars():
    print(user.name)
# 查询模型单个属性时,不需要使用 scalars
results = session.execute(select(User.name))
for row in results:
    print(row.name)
# 按照 主键id 查询还有一个快捷方式:
user = session.get(User, pk=1)
Update(更新数据)
python 复制代码
# 直接修改映射模型对象属性并提交
with Session() as session:
    # 查询要更新的对象
    review = session.get(CarReview, pk=1)
    # 修改属性
    review.label_workflow = "approved"
    review.opinion = "Updated after approval"
    session.commit()  # 自动检测变更并生成 UPDATE 语句(要求表必须有唯一标识字段)
 
# 使用 update() 方式更新数据
with Session() as session:
    # session.merge(review)  # 将游离对象重新添加到新会话(orm映射模型对象的更新数据方式);注:先查询再更新,不推荐这种方式
    stmt = update(CarReview).where(CarReview.id == review.id).values(
        label_workflow=review.label_workflow, is_accuracy=review.is_accuracy)
    session.execute(stmt)
    session.commit()
# synchronize_session 有三种选项: false, "fetch", "evaluate",默认是 evaluate
# false 表示完全不更新 Python 中的对象
# fetch 表示重新从数据库中加载一份对象
# evaluate 表示在更新数据库的同时,也尽量在 Python 中的对象上使用同样的操作
stmt = update(User).where(User.name == "john").values(name="John").execution_options(synchronize_session="fetch")
session.execute(stmt) 
session.commit()

# 执行原生 SQL 更新操作方式
with Session() as session:
    stmt = text("UPDATE car_reviews SET label_workflow = :value WHERE id = :id")
    session.execute(stmt, {"value": "New Value", "id": 1})
    session.commit()

# 这里有一个可能引入 race condition(竞态条件)的错误!如果两个进程同时更新这个值,可能导致只更新了一个值。
# 两者都赋值为自身认为的正确值 2,实际正确值为 1 + 1 + 1 = 3
# 对应 SQL:Update users set visit_count = 2 where user.id = 1
user.visit_count += 1
# 正确做法:注意大写的 U,也就是使用了模型对象的属性,生成的 SQL 是在 SQL 服务端 +1
# 对应 SQL: Update users set visit_count = visit_count + 1 where user.id = 1
user.visit_count = User.visit_count + 1
Delete(删除数据)
python 复制代码
with Session() as session:
    review = session.get(CarReview, 1)
    if review:
        session.delete(review)  # 标记删除
        session.commit()
     
# 注:session.flush()  # flush 并不是 commit,并没有提交事务,应该是可重复读,和数据库的隔离级别有关。

从 1.X API 迁移到 2.0 API

python 复制代码
- session.query(User).get(42)
+ session.get(User, 42)

- session.query(User).all()
+ session.execute(select(User)).scalars().all()

- session.query(User).filter_by(name="some_user").one()
+ session.execute(select(User).filter_by(name="some_user")).scalar_one()

- session.query(User).from_statememt(text("select * from users")).a..()
+ session.execute(select(User).from_statement(text("selct * from users"))).scalars().all()

- session.query(User).filter(User.name == "foo").update({"fullname": "FooBar"}, synchronize_session="evaluate")
+ session.execute(update(User).where(User.name == "foo").values(fullname="FooBar").execute_options(synchronize_session="evaluate"))

主要 import 分布

SQLAlchemy 的导出类型主要分布在以下三个部分:

python 复制代码
# SQL 相关的直接从根目录导入,值得注意的是 Column 和 Integer 也在这里
from sqlalchemy import text, insert, select, create_engine, Column, Integer, String

# ORM 相关的从 orm 子包中导入
from sqlalchemy.orm import declarative_base, Session, sessionmaker

# 异常从 exc 中导入
from sqlalchemy.exc import IntegrityError, SQLAlchemyError

从数据库反射创建ORM模型

通过 inspect 反射元数据生成代码

可以通过 inspect 模块提取数据库表的元数据,并动态生成 映射模型类 的定义代码。

python 复制代码
from sqlalchemy import inspect
from sqlalchemy.ext.automap import automap_base
from utils.sqlalchemy_manager import get_sync_sqlclient

# 创建数据库引擎
db = get_sync_sqlclient()
engine = db.sync_engine

# 自动映射表
Base = automap_base()
Base.prepare(autoload_with=engine)

# 获取 Inspector 对象
inspector = inspect(engine)

# 类型映射字典(根据需要扩展)
type_mapping = {
    'INTEGER': 'Integer',
    'VARCHAR': 'String',
    'TEXT': 'Text',
    'DATETIME': 'DateTime',
    'DATE': 'Date',
    'BOOLEAN': 'Boolean',
    'FLOAT': 'Float',
    'BIGINT': 'BigInteger',
    'DECIMAL': 'Numeric'
}

# 获取所有表名
table_names = inspector.get_table_names()

# 动态生成类定义代码
for table_name in table_names:
    class_name = ''.join([word.capitalize() for word in table_name.split('_')])
    print(f"class {class_name}(Base):")
    print(f"    __tablename__ = '{table_name}'")

    columns = inspector.get_columns(table_name)
    for column in columns:
        column_name = column['name']
        column_type = type_mapping.get(str(column['type']).split('(')[0], 'String')
        nullable = column['nullable']
        default = column['default']

        if column.get('autoincrement', False):
            primary_key = 'primary_key=True, '
        else:
            primary_key = ''

        # 如果有注释,添加注释
        if column.get("comment"):
            print(f"    # {column['comment']}")

        # 生成列定义代码
        print(f"    {column_name} = Column({column_type}, {primary_key}nullable={nullable}, default={default})")

    print("\n")

打印输出的表模型结构如下:

python 复制代码
class TUser(Base):
    __tablename__ = 't_user'
    id = Column(Integer, primary_key=True, nullable=False, default=None)
    name = Column(String, nullable=False, default=None)
    age = Column(Integer, nullable=True, default=None)
    sex = Column(String, nullable=True, default=None)
    email = Column(String, nullable=True, default=None)

sqlacodegen 工具(仅支持1.4以下版本)

sqlacodegen 是第三方工具,可直接从数据库生成完整的 ORM 模型代码:

安装工具

sh 复制代码
pip install sqlacodegen

生成代码并保存到文件

sh 复制代码
sqlacodegen mysql+pymysql://user:pass@host/dbname > models.py

异步支持(AsyncIO)

SQLAlchemy 2.0 原生支持异步操作(需使用 AsyncSession):

python 复制代码
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession

async_engine = create_async_engine("postgresql+asyncpg://user:password@localhost/db")
AsyncSession = sessionmaker(async_engine, class_=AsyncSession)

async with AsyncSession() as session:
    stmt = select(CarReview)
    result = await session.execute(stmt)
    reviews = result.scalars().all()

以上为 SQLAlchemy 2.0 的 ORM层相关概念及基本使用,通过 SQLAlchemy 官方文档来看,SQLAlchemy 的使用功能非常丰富,涵盖了从基础到高级的多个方面,感兴趣的读者可以查阅官网了解更全面的功能实现。

参考资料


SQLAlchemy 2.0 官方文档 - MySQL and MariaDB

SQLAlchemy 2.0 中文文档

SQLAlchemy 2.0 - masantu博客教程

相关推荐
大大大大晴天3 小时前
每天认识一个组件:数据治理Apache Atlas
大数据
数字新视界3 小时前
2026模块化机房选型指南:行业市场发展趋势、占有率与竞争梯队分析报告解析
大数据·人工智能·物联网·数据中心·微模块机房·模块化机房·冷通道
姜穆澜5 小时前
OneID 从 0 到 1 完整生产案例(四)
大数据
OsDepK6 小时前
项目快速Git至仓库(完整版)
大数据·git·elasticsearch·搜索引擎
合米AI SOP系统6 小时前
传统产线如何快速上马落地 AI 防错?合米科技 AI SOP 7天即可上线。
大数据·人工智能·科技
思录Echo6 小时前
什么决定具身智能的最终走向?多技术路线与落地现实辨析
大数据·人工智能
xiaohaiAIgeo7 小时前
【2026年】AI监控加行为分析守护实验室安全
大数据·人工智能·科普知识
林墨聊AIGC7 小时前
动漫AI视频创作工具在哪找到的?2026年最新动漫AI视频平台与软件指南
大数据·人工智能·ai作画·aigc·音视频
故七月9 小时前
告别 AI 时代品牌 “隐身”:万域智瞰 AI‑GEO,构建品牌大模型时代营销新基建
大数据·人工智能
数字孪生视频孪生10 小时前
三维实时重构异构底座 核工危化无感定位跨境轨迹一屏统揽
大数据·运维·人工智能·重构·架构