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 的使用功能非常丰富,涵盖了从基础到高级的多个方面,感兴趣的读者可以查阅官网了解更全面的功能实现。