跳转至

11.0 Databases and ORM

1. MVC architecture and how it maps to web development

  • PurposeModel-View-Controller (MVC) 是一种流行的服务器端 Web 应用程序架构设计模式。它描述了如何组织概念。
  • Mapping to Web Development
    • Model:指应用程序中由数据库支持的 Python 对象(例如 SQLite)。一个 Model 是与数据库中的一个实体配对的对象。
    • View:指由 Jinja 模板创建的 HTML 页面。
    • Controller:指 routes.py 中的代码,负责连接 Model 和数据库,根据 Model 准备 View,以及更新和保存 Model 回数据库。
  • Advantages:将 Web 应用程序划分为 MVC 具有多种优点:
    • 可以修改 View 而不改变 Model。
    • 可以替换数据库而不改变 View。
    • 专业开发者可以分别处理各自的部分。
    • 可以支持多个 View(例如,网页 + 移动应用)。
    • 更容易隔离测试各部分。
  • Designing:要设计 MVC 解决方案架构,需要确定所需的 Model、View 和 Controller。可以从用户故事中识别名词作为 Model,动词作为 routes(Controller 的一部分),并为指定用户关联 View。还可以模拟 View 的线框图(wireframe sketches),展示用户界面的基本布局和功能。也可以模拟典型的 HTTP 请求和响应。

2. Purpose, pros, cons of an ORM

  • PurposeObject-Relational Mapping (ORM) 库会自动创建对应于数据库表中每一行的对象,并允许通过原生代码(如 Python)直接操作数据库。它在数据库中的数据和应用程序中的 Model 之间建立链接。ORM 允许您通过对象与数据库交互,而无需编写实际的 SQL 查询。
  • Pros
    • 加快开发时间,因为实现 Model 的类会自动生成相应的 SQL 代码。
    • 抽象了数据库的具体实现——例如,可以在不改变代码的情况下从 SQLite 切换到 MySQL。
    • 使与数据库的交互更加“Pythonic”和基于对象。
    • 比编写原始 SQL 查询更容易。
  • Cons
    • 有时性能会降低——库为您生成 SQL 查询,这些查询可能并非总是最优的。
    • 可能难以理解您的代码正在执行的具体数据库操作。
  • Usage:在课程项目组中,您将被要求使用 ORM 库。课程中使用 SQLAlchemy 作为 ORM。如果您在项目中使用纯 SQL 查询,将会扣分。

3. The very basics from your previous database course.

  • Databases:是数据的有组织集合。需要将它们链接到 Web 应用程序以存储用户信息。
  • Types:有关系型数据库 (relational databases)、文档数据库 (document databases)、图数据库 (graph databases) 等。Flask 可以支持多种数据库。关系型数据库是最流行和通用的方法。NoSQL 指“不仅仅是 SQL”,描述非关系型数据库。
  • Relational Databases:将数据存储为一组关系 (relations),每个关系表示为一个表 (table)。表的每一行是一个实体 (entity),每一列是该实体的属性 (attribute)。每个关系都有一个对于该关系中每个实体都唯一的属性,称为 primary key。一些关系的属性是其他关系中的 primary key,这些属性称为 foreign keys
  • DBMSDatabase Management System (DBMS) 是控制对数据库访问的应用程序。课程使用关系型数据库 SQLite 作为 DBMS,因为它设置简单、包体积小、将数据库存储为单个文件。缺点包括扩展性不佳、没有内置认证方法。
  • Schema:数据库的 schema 是定义的表 (关系) 集合、属性类型以及属性约束。这是数据库的元数据 (meta-data),在应用程序的正常使用中不应改变。
  • CRUDCreate, Read, Update, Delete (CRUD) 是任何持久化存储系统的基本操作。
  • SQLSequential Query Language (SQL) 提供了执行 CRUD 操作的语法:Create 使用 insert 语句;Read 使用 select 语句;Update 使用 update 语句;Delete 使用 delete 语句。

4. At a high-level how SQLAlchemy can be used to perform operations on the database via Python.

  • SQLAlchemy 用于将 SQLite 数据库链接到 Flask 应用程序。
  • 需要安装 flask-sqlalchemyflask-migrate 包。
  • 链接步骤:配置 Flask 应用程序对象,创建一个名为 db 的 SQLAlchemy 对象,创建一个 migrate 对象,导入 models 模块。
  • Adding Entities
    • 创建 Model 的实例。
    • 使用 db.session.add() 将实例添加到 db.session 对象。
    • 注意,此时更改尚未保存到数据库,仅通知 DBMS 这些对象存在。
    • 必须调用 db.session.commit() 才能将所有更改与数据库同步。这类似于 git add 后再 git commit
    • Example
      from app import db
      from app.models import Group # 假设 Group 是一个模型
      
      # 创建 Group 实例
      new_group = Group(group_id=1, group_name='Group Alpha')
      # 添加到 session
      db.session.add(new_group)
      # 提交到数据库
      db.session.commit()
      
  • Basic Queries
    • 使用 .<model>.querysession.query(<model>) 从数据库中提取实体。
    • .all():返回该类型的所有实体。
    • .get(i):获取 primary key 为 i 的 Model 实例。
    • Example
      from app.models import Group # 假设 Group 是一个模型
      
      # 获取所有 Group
      all_groups = Group.query.all()
      # 获取 primary key 为 1 的 Group
      group_one = Group.query.get(1)
      
  • Deleting Entities
    • 使用 db.session.delete() 删除 Model 实例。
    • 必须调用 db.session.commit() 才能将更改反映到数据库。
    • Example
      from app import db
      from app.models import Group # 假设 Group 是一个模型
      
      # 获取要删除的 Group (例如 primary key 为 1)
      group_to_delete = Group.query.get(1)
      # 删除实例
      db.session.delete(group_to_delete)
      # 提交到数据库
      db.session.commit()
      
  • More Complicated Queries:可以使用链式查询语法执行复杂操作。
    • query.join():执行内连接 (inner joins)。
    • query.outerjoin():执行左外连接 (left-outer-joins)。
    • filter_by():过滤结果 (类似 SQL 的 WHERE 子句)。
    • order_by():排序结果 (类似 SQL 的 ORDER BY 子句)。

5. The meaning of basic SQLAlchemy syntax for defining models

  • Models 在 app/models.py 文件中定义。
  • 导入 db (SQLAlchemy 类的实例)。
  • 定义继承自 db.Model 的类。每个这样的类对应数据库中的一个表。
  • 使用 db.Column 定义列。
    • 第一个参数定义列的类型。有多种类型可用,包括 string, integer, text, datetime, float, boolean, pickle type, large binary 等。
    • 可选的列参数包括 nullable (是否允许为空), primary_key (是否是主键), foreign_key (是否是外键), unique (值是否唯一), index (是否创建索引) 等。
  • Example Model Definition
    from app import db
    
    class Student(db.Model):
        student_id = db.Column(db.Integer, primary_key=True) # 整型主键
        student_name = db.Column(db.String(50), nullable=False) # 字符串类型,最大长度50,不允许为空
        group_id = db.Column(db.Integer, db.ForeignKey('group.group_id')) # 整型外键,引用 group 表的 group_id
        # relationship 定义在此处或 Group 模型中
    
    class Group(db.Model):
        group_id = db.Column(db.Integer, primary_key=True) # 整型主键
        group_name = db.Column(db.String(50)) # 字符串类型
        # relationship 定义在此处或 Student 模型中
    
  • Flask-SQLAlchemy 可以从 Model 中自动提取数据库表的 schema。Model 类定义了数据库的 schema。

6. Purpose of SQLAlchemy relationships

  • Purpose:Foreign key 允许访问链接对象的 ID,但需要运行单独的查询来查找对象。使用 db.relationship 可以直接将链接对象作为类的字段访问。这使得访问链接对象更加直观和“Pythonic”。
  • Usage:在 Model 中使用 db.relationship 定义关系。
  • Access:定义关系后,可以直接访问和分配链接对象,就像访问普通变量一样。例如,如果 Student Model 与 Group Model 有关系,可以通过 student_instance.group 直接获取关联的 Group 对象。
  • Back-populatesback-populates 参数链接双向关系。如果更新 group.students,那么每个相关 student.group 字段也会更新。这确保了所有关联对象同时更新。
  • Types:使用 db.relationship 方法可以创建一对一 (one-to-one)、一对多 (one-to-many) 和多对多 (many-to-many) 关系。
  • Example Relationship Definition
    from app import db
    
    class Student(db.Model):
        # ... 其他列定义 ...
        group_id = db.Column(db.Integer, db.ForeignKey('group.group_id'))
        group = db.relationship('Group', back_populates='students') # 定义与 Group 模型的关系,back-populates 到 Group 模型的 'students' 字段
    
    class Group(db.Model):
        # ... 其他列定义 ...
        students = db.relationship('Student', back_populates='group') # 定义与 Student 模型的关系,back-populates 到 Student 模型的 'group' 字段
    

7. Purpose and high-level overview of how Database migrations work

  • Purpose:数据库迁移 (Database migration) 是必要的,因为在应用程序开发过程中,Model 会频繁变更。它允许在不破坏现有数据的情况下更新数据库 schema。它就像数据库的版本控制,允许在不同版本之间前进或回退。这对于拥有大量用户或有价值数据的大型应用程序至关重要。
  • How it works (High-level)
    • 每个数据库更改都附带升级 (upgrade) 和降级 (downgrade) 脚本。这些脚本用于将数据库从一个版本移动到 Model 的前一个版本。
    • SQLAlchemy 使用 Alembic 迁移框架,由 flask-migrate 包包装。
    • Initialization:运行 flask db init 命令初始化数据库并创建迁移文件夹。
    • Creating Migrations
      • flask db revision:手动创建迁移脚本。
      • flask db migrate:自动创建迁移脚本。它将更新的 Model 类与数据库的当前状态进行比较。生成的脚本包含 upgrade()downgrade() 方法。
    • Applying Migrationsflask db upgrade 应用脚本使数据库与 Model 同步。
    • Rolling Backflask db downgrade 回滚更改到上一个版本。
  • Importance:确保数据库 schema 和 Model 保持同步。在项目组中,需要执行至少两次数据库迁移才能获得最高分。
  • Version Control:迁移脚本应该上传到 Git 仓库。它们跟踪 schema 的变化,帮助团队成员同步。数据库文件本身 (app.db) 不应提交到 Git 仓库。

8. Best practice for storing images

  • Direct Storage:可以使用 Blob 列类型将图像直接存储在数据库中。但这性能很差,因为数据库并非设计用于存储此类数据。
  • Small-scale Projects:将图像存储在服务器的文件系统中,并在数据库中存储指向该文件的路径 (path)。这是一种更好的方法。
  • Large-scale Projects:最好将图像存储在服务器之外的专用硬件上。这些硬件针对重度 IO 操作进行了优化,而服务器硬件并未优化。例如,将图像存储在 Amazon bucket 等外部服务上。

9. Considerations when building a real-life webserver

  • 当应用程序达到一定规模时,数据库必须支持更多功能。
  • Indexing:为频繁使用的数据添加索引 (indexes),以提高查询性能。
  • Data Layout Optimisation:根据数据是经常读取还是写入来优化数据布局。
  • Caching:缓存结果以避免重复执行相同的查询。对于多个用户频繁请求相同或最新数据时尤其有用。
  • Security:数据库安全性至关重要。即使服务器被攻破,也要阻止恶意用户或应用程序访问数据库。需要在服务器上实施安全机制。
  • Backups:执行备份以避免服务器故障时丢失数据库。对于大型项目非常重要。
  • Auditing:审计 (auditing) 以跟踪数据库何时被谁更改。这对于管理数据的“黄金副本”和了解谁进行了哪些更改非常重要。