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博客教程

相关推荐
爱搞技术的猫猫2 小时前
微店商品详情API接口实战指南:从零实现商品数据自动化获取
大数据·linux·运维·数据库·自动化
南宫文凯3 小时前
Storm实时流式计算系统(全解)——上
大数据·storm
Python数据分析与机器学习3 小时前
《基于Django和ElasticSearch的学术论文搜索推荐系统的设计与实现》开题报告
大数据·开发语言·python·elasticsearch·搜索引擎·django·课程设计
StarRocks_labs4 小时前
小红书湖仓架构的跃迁之路
大数据·架构·spark·湖仓一体·lakehouse
青春不流名4 小时前
部署Flink1.20.1
大数据
乙真仙人4 小时前
构建高效大数据监督的三要素
大数据
Elastic 中国社区官方博客6 小时前
Elasticsearch:使用阿里云 AI 服务进行嵌入和重新排名
大数据·数据库·人工智能·elasticsearch·搜索引擎·阿里云·云计算
狮歌~资深攻城狮9 小时前
Flink怎么搞CDC?
大数据
狮歌~资深攻城狮9 小时前
Flink事件时间和处理时间咋区分
大数据
青云交9 小时前
Java 大视界 -- 基于 Java 的大数据分布式缓存一致性维护策略解析(109)
java·大数据·redis·分布式·分布式缓存·redlock·一致性维护