Files
PythonLearn/04_数据库/4_3_SQLAlchemy基础/practice.py
T

163 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第4-3课练习:使用SQLAlchemy 2.x管理课程商品
#
# 本文件只提供题目,不包含导入、代码骨架、测试数据或参考答案。
# 练习会创建course_orm_product表,并只操作ORM-P-前缀的数据。
# 请勿改用现有业务表,也不要删除不属于本练习的数据。
# 第一部分:导入、配置与声明式基类
# 1. 导入Decimal、Path和tomllib。
# 2. 从sqlalchemy导入Numeric、String、URL、create_engine、delete和select。
# 3. 从sqlalchemy.orm导入DeclarativeBase、Mapped、Session、mapped_column和sessionmaker。
# 4. 使用Path(__file__).with_name("config.toml")定义CONFIG_PATH。
# 5. 定义Base(DeclarativeBase),类体中不添加业务字段。
# 6. 实现load_database_config(config_path),读取并返回[postgresql]配置字典。
# 7. 实现create_database_url(database_config),必须调用URL.create()并指定:
# - drivername="postgresql+psycopg";
# - username、password、host、port、database分别来自配置;
# - 返回URL对象,不手工拼接包含密码的字符串。
# 第二部分:定义Product ORM模型
# 1. 定义Product(Base)。
# 2. 设置__tablename__ = "course_orm_product"。
# 3. 使用Mapped和mapped_column()声明:
# - id:int主键,由数据库生成;
# - code:最长30字符,唯一且非空;
# - name:最长100字符且非空;
# - price:NUMERIC(10, 2)且非空;
# - stock:int且非空。
# 4. 定义__repr__,至少包含id、code、name和stock,返回字符串供调试使用。
#
# Java对照提醒:
# - Product既是普通Python类,也是数据库映射模型;
# - Mapped类似声明持久化属性的类型;
# - mapped_column()描述列约束,不等于Java字段的setter。
# 第三部分:创建并初始化课程数据
# 1. 定义reset_practice_data(session),调用session.execute()执行:
# delete(Product).where(Product.code.like("ORM-P-%"))。
# 2. 定义add_products(session),创建并保存以下两个Product:
# - code="ORM-P-001",name="机械键盘",price=Decimal("399.00"),stock=10;
# - code="ORM-P-002",name="无线鼠标",price=Decimal("199.00"),stock=20。
# 3. 调用session.add_all(products)。
# 4. 调用session.flush(),然后读取第一件商品的id并输出:
# “flush后第一件商品ID:实际ID”。
# 5. 不在add_products()中调用commit()。
# 第四部分:实现查询
# 1. 定义find_products(session),创建SQLAlchemy 2.x查询语句:
# select(Product).where(Product.code.like("ORM-P-%")).order_by(Product.code)
# 2. 调用session.scalars(statement).all()取得Product对象。
# 3. 返回Product对象列表;没有数据时返回空列表,不返回None。
# 4. 不使用旧式session.query()。
# 第五部分:实现修改和删除
# 1. 定义update_product(session):
# - 调用session.scalar(select(Product).where(Product.code == "ORM-P-001"));
# - 找不到时抛出RuntimeError("没有找到商品ORM-P-001。");
# - 找到后直接把stock属性修改为8;
# - 不手写UPDATE SQL,也不在方法中提交。
# 2. 定义delete_product(session):
# - 查询code为ORM-P-002的Product;
# - 找不到时抛出RuntimeError("没有找到商品ORM-P-002。");
# - 调用session.delete(product);
# - 不手写DELETE SQL,也不在方法中提交。
# 第六部分:验证事务回滚
# 1. 定义demonstrate_rollback(session_factory)。
# 2. 在try中使用with session_factory.begin() as session管理事务。
# 3. 查询ORM-P-001,把stock修改为0。
# 4. 调用session.flush(),让UPDATE先发送给数据库。
# 5. 紧接着抛出RuntimeError("模拟库存业务失败。")。
# 6. 在事务with外捕获RuntimeError并输出:
# “失败事务已回滚:模拟库存业务失败。”
# 7. 最终回查时stock必须仍为8,而不是0。
# 第七部分:输出和main()流程
# 1. 定义print_products(title, products),先print(title),再逐个输出:
# “ORM-P-001|机械键盘|价格:399.00|库存:10”。
# 2. main()依次执行:
# - 读取TOML配置并创建URL;
# - 调用create_engine(),除database_url外还要传入:
# connect_args={"connect_timeout": 配置值或默认值10}、pool_size=5、
# max_overflow=5、pool_pre_ping=True、echo=False;
# - 调用sessionmaker(engine, expire_on_commit=False)创建Session工厂;
# - 调用Base.metadata.create_all(engine)创建缺失的课程表;
# - 使用with session_factory.begin() as session重置并新增商品;
# - 使用with session_factory() as session查询并输出“新增后:”;
# - 使用新的session_factory.begin()事务修改和删除;
# - 使用只读Session查询并输出“修改并删除后:”;
# - 调用demonstrate_rollback(session_factory);
# - 最后查询并输出“失败事务回滚后:”。
# 3. 在程序边界分别处理配置错误和数据库访问错误。
# 4. 添加程序入口判断并调用main()。
# 预期关键输出:
# flush后第一件商品ID:实际ID
# 新增后:
# ORM-P-001|机械键盘|价格:399.00|库存:10
# ORM-P-002|无线鼠标|价格:199.00|库存:20
# 修改并删除后:
# ORM-P-001|机械键盘|价格:399.00|库存:8
# 失败事务已回滚:模拟库存业务失败。
# 失败事务回滚后:
# ORM-P-001|机械键盘|价格:399.00|库存:8
# 自查清单:
# 1. 是否使用SQLAlchemy 2.x的DeclarativeBase、Mapped和mapped_column()?
# 2. Engine是否只创建一次,并启用了pool_pre_ping?
# 3. 是否使用sessionmaker统一创建Session?
# 4. 是否使用select()和session.scalars(),而不是session.query()?
# 5. 修改属性后是否由Session自动识别变化?
# 6. Repository式函数中是否没有擅自提交事务?
# 7. flush后是否能取得数据库生成的主键,但事务仍可回滚?
# 8. 失败事务回滚后库存是否仍为8?
# 9. 是否只清理ORM-P-前缀的练习数据?
# 最终验收标准:
# 1. practice.py通过语法检查并能重复运行;
# 2. 模型字段与数据库表映射正确;
# 3. 新增、查询、修改和删除结果符合预期;
# 4. Session事务成功时提交、异常时回滚;
# 5. 能解释Engine、连接池和Session的职责;
# 6. 能解释flush()与commit()的区别;
# 7. 不使用SQLAlchemy 1.x旧式查询写法;
# 8. config.toml与真实数据库信息没有进入Git。