feat(数据库): 完善第四阶段SQLAlchemy课程与综合项目
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
# 第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。
|
||||
Reference in New Issue
Block a user