深入浅出 SQLAlchemy:初学者指南 – wiki基地


深入浅出 SQLAlchemy:初学者指南

数据是现代应用程序的基石。无论是简单的博客应用、复杂的电商平台,还是精密的科学计算软件,都离不开数据的存储、管理和交互。关系型数据库(如 PostgreSQL, MySQL, SQLite)因其结构化、 ACID 特性等优点,成为了数据存储的主流选择。

然而,在 Python 应用程序中直接使用 SQL 语句与数据库交互,往往会带来一些挑战:

  1. 代码冗余且难以维护: 重复编写相似的 SQL 语句(SELECT, INSERT, UPDATE, DELETE),当数据库 Schema 发生变化时,需要修改大量 SQL 代码。
  2. SQL 注入风险: 手动拼接 SQL 字符串容易引入安全漏洞。
  3. 数据与对象的不匹配: 关系型数据库以表格形式存储数据,而 Python 是面向对象的语言。将数据库中的行转换为 Python 对象,或将 Python 对象转换为数据库行,需要编写大量的映射代码。这种“阻抗失配”是开发中的常见痛点。
  4. 数据库移植性差: 不同数据库系统的 SQL 语法和特性存在差异,切换数据库时可能需要重写部分 SQL 代码。

为了解决这些问题,对象关系映射 (Object-Relational Mapping, ORM) 技术应运而生。ORM 的核心思想是将数据库中的表格(Table)映射到程序中的类(Class),将表格中的行(Row)映射到类的对象(Object),将行的字段(Column)映射到对象的属性(Attribute)。通过 ORM,开发者可以使用面向对象的方式来操作数据库,而无需直接编写繁琐的 SQL 语句。

在 Python 社区中,SQLAlchemy 是最著名、功能最强大且最受推崇的 ORM 框架之一。它不仅仅是一个 ORM,更是一个完整的数据库工具包,提供了灵活且高性能的数据访问能力。本篇文章将带你深入 SQLAlchemy 的世界,从基础概念到实际应用,帮助你轻松入门。

什么是 SQLAlchemy?

SQLAlchemy 是一个 Python SQL 工具包和对象关系映射器 (ORM)。它的设计哲学是提供强大且灵活的数据访问能力,既支持高度抽象的 ORM 层,也支持底层的 SQL 表达式构建,允许开发者在需要时“下沉”到 SQL 层面,充分利用数据库的特性。

SQLAlchemy 主要由两个部分组成:

  1. SQLAlchemy Core: 这是一个底层的、基于 SQL 表达式的工具包。它提供了连接池、事务管理、SQL 语句的构建和执行等功能。使用 Core,你可以用 Python 代码构建 SQL 语句,然后执行它们,并处理结果集。它类似于一个高级的数据库连接库,但提供了更强大的 SQL 构建能力,能够抽象不同数据库的方言差异。
  2. SQLAlchemy ORM: 这是在 Core 之上构建的对象关系映射层。它将 Python 类映射到数据库表,将对象操作转换为 SQL 语句,让你以面向对象的方式进行数据持久化和查询。ORM 层极大地提高了开发效率,特别是对于复杂的应用程序。

SQLAlchemy 的优势:

  • 灵活性: 可以在 Core 和 ORM 之间自由选择或混合使用,满足不同的需求。对于需要精细控制 SQL 的场景,可以使用 Core;对于大部分 CRUD(创建、读取、更新、删除)操作,ORM 更为便捷。
  • 强大且成熟: SQLAlchemy 拥有悠久的历史和庞大的用户社区,功能丰富,性能经过优化,支持多种数据库系统。
  • 高性能: 底层 Core 的设计使得 SQLAlchemy 在性能上表现出色,可以根据需要调整连接池、事务等配置。
  • 数据库兼容性好: 通过方言(Dialect)支持多种数据库,如 PostgreSQL, MySQL, SQLite, Oracle, Microsoft SQL Server 等。

准备工作:安装与连接数据库

开始使用 SQLAlchemy 之前,首先需要安装它。

bash
pip install sqlalchemy

此外,你还需要安装对应数据库的驱动程序。例如:

  • SQLite: Python 标准库自带 sqlite3,无需额外安装驱动。
  • PostgreSQL: pip install psycopg2
  • MySQL: pip install mysql-connector-pythonpip install PyMySQL

在本指南中,我们将使用 SQLite 数据库,因为它无需额外安装和配置,非常适合初学者。

连接数据库是使用 SQLAlchemy 的第一步。无论是 Core 还是 ORM,都需要一个 Engine 对象来管理数据库连接。

create_engine() 函数用于创建一个 Engine 实例:

“`python
from sqlalchemy import create_engine

连接到 SQLite 数据库文件 mydatabase.db

如果文件不存在,会自动创建

engine = create_engine(‘sqlite:///mydatabase.db’)

连接到内存中的 SQLite 数据库 (数据不会持久化)

engine = create_engine(‘sqlite:///:memory:’)

示例:连接到 PostgreSQL 数据库

engine = create_engine(‘postgresql://user:password@host:port/database’)

示例:连接到 MySQL 数据库

engine = create_engine(‘mysql+mysqlconnector://user:password@host:port/database’)

“`

数据库 URL 格式通常是 dialect+driver://user:password@host:port/database。对于 SQLite 文件,只需要 sqlite:///path/to/database.db

engine 对象代表了与特定数据库的连接池和方言。你可以通过 engine 来获取连接并执行命令。

SQLAlchemy Core:构建 SQL 的艺术

虽然 ORM 更常用,但理解 Core 是非常有益的,因为它展示了 SQLAlchemy 的底层工作原理,并且在某些场景下直接使用 Core 可能更合适。

Core 的核心组件包括:

  • MetaData:一个容器对象,用于收集关于数据库结构的信息,比如表、列等。
  • Table:表示数据库中的一个表格。
  • Column:表示表格中的一个列。
  • Type:表示列的数据类型(如 Integer, String, DateTime 等)。
  • SQL Expression Constructs:用于构建 SELECT, INSERT, UPDATE, DELETE 等 SQL 语句的 Python 对象。

定义表结构

使用 Core 定义表结构通常涉及创建一个 MetaData 对象,并在其关联的 Table 对象中定义 Column

“`python
from sqlalchemy import MetaData, Table, Column, Integer, String

创建一个 MetaData 对象

metadata = MetaData()

定义一个名为 ‘users’ 的表

users_table = Table(
‘users’, metadata,
Column(‘id’, Integer, primary_key=True), # 定义主键列
Column(‘name’, String(50), nullable=False), # 定义字符串列,非空
Column(‘age’, Integer), # 定义整数列
)

定义另一个名为 ‘posts’ 的表

from sqlalchemy import Text, DateTime, ForeignKey
from datetime import datetime

posts_table = Table(
‘posts’, metadata,
Column(‘id’, Integer, primary_key=True),
Column(‘title’, String(100), nullable=False),
Column(‘content’, Text),
Column(‘author_id’, Integer, ForeignKey(‘users.id’)), # 定义外键
Column(‘created_at’, DateTime, default=datetime.now), # 带默认值的日期时间列
)
“`

这里,我们定义了 usersposts 两张表,并建立了 posts.author_idusers.id 的外键关系。MetaData 对象 metadata 收集了这两张表的定义。

创建表

定义好表结构后,可以使用 MetaData 对象通过 engine 在数据库中创建这些表。

“`python

使用 engine 连接创建所有定义的表

metadata.create_all(engine)
print(“表已创建!”)
“`

create_all(engine) 方法会检查数据库中是否已存在同名的表,如果不存在则执行 CREATE TABLE 语句。

插入数据 (Core)

使用 Core 插入数据,需要构建一个 insert 表达式,并通过连接执行它。

“`python
from sqlalchemy import insert

获取一个数据库连接

with engine.connect() as conn:
# 构建插入表达式
insert_stmt = insert(users_table).values(name=’Alice’, age=30)

# 执行插入操作
result = conn.execute(insert_stmt)

# 获取新插入记录的主键 (如果数据库支持)
inserted_user_id = result.lastrowid
print(f"插入用户 Alice,ID: {inserted_user_id}")

# 插入多条数据
more_users = [
    {'name': 'Bob', 'age': 25},
    {'name': 'Charlie', 'age': 35},
]
conn.execute(insert(users_table), more_users)
print("插入用户 Bob 和 Charlie")

# 提交事务 (使用 with 语句会自动提交或回滚)
# conn.commit() # 如果不使用 with conn.begin(): 或 with engine.connect():

“`

注意,with engine.connect() as conn: 语句块会自动管理连接的获取和释放,并且通常默认在块结束时提交事务(或者在发生异常时回滚)。更明确的事务管理方式是使用 with conn.begin():

查询数据 (Core)

使用 Core 查询数据,需要构建一个 select 表达式。

“`python
from sqlalchemy import select

with engine.connect() as conn:
# 构建 SELECT * FROM users 表达式
select_stmt = select(users_table)

# 执行查询并获取结果集
result_set = conn.execute(select_stmt)

# 遍历结果集
print("\n所有用户:")
for row in result_set:
    # 每一行是一个 RowProxy 对象,可以通过列名或索引访问
    print(f"ID: {row['id']}, Name: {row['name']}, Age: {row['age']}")
    # print(f"ID: {row.id}, Name: {row.name}, Age: {row.age}") # 也可以通过属性访问

# 查询特定条件的数据 (WHERE)
select_where_stmt = select(users_table).where(users_table.c.age > 30) # c 是 columns 的缩写
print("\n年龄大于 30 的用户:")
for row in conn.execute(select_where_stmt):
    print(row)

# 查询特定列
select_cols_stmt = select(users_table.c.name, users_table.c.age).where(users_table.c.name == 'Bob')
print("\n查询 Bob 的姓名和年龄:")
bob_info = conn.execute(select_cols_stmt).fetchone() # 只获取第一行
if bob_info:
    print(f"Name: {bob_info.name}, Age: {bob_info.age}")

# 使用 ORDER BY
select_order_stmt = select(users_table).order_by(users_table.c.name)
print("\n按姓名排序的用户:")
for row in conn.execute(select_order_stmt):
    print(row)

“`

Core 提供了丰富的表达式构建能力,可以轻松实现各种复杂的 SQL 查询,包括 JOIN, GROUP BY, HAVING 等。

更新数据 (Core)

使用 Core 更新数据,需要构建一个 update 表达式。

“`python
from sqlalchemy import update

with engine.connect() as conn:
# 构建 UPDATE users SET age = age + 1 WHERE name = ‘Alice’ 表达式
update_stmt = update(users_table).where(users_table.c.name == ‘Alice’).values(age=users_table.c.age + 1)

# 执行更新
result = conn.execute(update_stmt)
print(f"\n更新了 {result.rowcount} 条 Alice 的记录")

# 提交事务
# conn.commit()

“`

删除数据 (Core)

使用 Core 删除数据,需要构建一个 delete 表达式。

“`python
from sqlalchemy import delete

with engine.connect() as conn:
# 构建 DELETE FROM users WHERE name = ‘Charlie’ 表达式
delete_stmt = delete(users_table).where(users_table.c.name == ‘Charlie’)

# 执行删除
result = conn.execute(delete_stmt)
print(f"删除了 {result.rowcount} 条 Charlie 的记录")

# 提交事务
# conn.commit()

“`

至此,我们已经了解了 SQLAlchemy Core 的基本用法,它提供了强大的 SQL 构建和执行能力。接下来,我们将进入 SQLAlchemy 的另一半:ORM。

SQLAlchemy ORM:面向对象操作数据库

SQLAlchemy ORM 在 Core 的基础上提供了一个更高级的抽象层,允许你将 Python 类映射到数据库表,并通过操作 Python 对象来间接操作数据库。

ORM 的核心概念:

  • Declarative System: 一种定义 ORM 模型的便捷方式,通过继承一个基类来声明类与表的映射关系。
  • Mapped Class (Model): 映射到数据库表的 Python 类。
  • Mapper: SQLAlchemy 在内部使用 Mapper 将类属性与表列关联起来。Declarative System 简化了 Mapper 的创建。
  • Session: ORM 的工作单元。Session 提供了数据库会话,负责加载、持久化对象,并管理事务。所有 ORM 操作(查询、添加、删除等)都通过 Session 进行。
  • Query: 用于构建和执行 ORM 查询的对象。

定义 ORM 模型 (Declarative System)

使用 Declarative System 定义模型是最常见的方式。首先需要创建一个基类。

“`python
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
from sqlalchemy.orm import relationship # 引入 relationship 用于定义关系
from datetime import datetime

创建一个 Declarative Base

Base = declarative_base()

定义 User 模型,它将映射到 users 表

class User(Base):
tablename = ‘users’ # 指定映射到的表名

id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
age = Column(Integer)

# 定义与 Post 模型的关系:一个用户可以有多篇帖子
# backref='author' 会在 Post 模型中自动创建一个 'author' 属性,指向 User 对象
posts = relationship('Post', backref='author')

def __repr__(self):
    return f"<User(id={self.id}, name='{self.name}', age={self.age})>"

定义 Post 模型,它将映射到 posts 表

class Post(Base):
tablename = ‘posts’

id = Column(Integer, primary_key=True)
title = Column(String(100), nullable=False)
content = Column(Text)
author_id = Column(Integer, ForeignKey('users.id')) # 定义外键列

created_at = Column(DateTime, default=datetime.now)

def __repr__(self):
    return f"<Post(id={self.id}, title='{self.title}', author_id={self.author_id})>"

“`

这里,我们定义了 UserPost 两个类,它们都继承自 Base__tablename__ 属性指定了它们映射的数据库表名。类属性(如 id, name, age)使用 Column 定义,并指定数据类型和约束。relationship 定义了 UserPost 之间的一对多关系:一个 User 可以有多个 Post

创建表 (ORM)

定义好模型后,创建表的方式与 Core 类似,使用 Base 对象的 metadata

“`python

假设 engine 已经创建

from sqlalchemy import create_engine

engine = create_engine(‘sqlite:///mydatabase_orm.db’)

使用 Base 的 metadata 创建所有定义的表

Base.metadata.create_all(engine)
print(“ORM 表已创建!”)
“`

创建 Session

Session 是 ORM 进行数据库操作的入口。你需要创建一个 sessionmaker,然后通过它创建 Session 实例。

“`python
from sqlalchemy.orm import sessionmaker

创建一个 session 工厂

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

创建一个 Session 实例

session = SessionLocal() # 这样创建的 session 需要手动关闭或使用 try/finally

推荐使用 context manager (with 语句) 来管理 Session

with SessionLocal() as session:

# 在这里进行数据库操作

pass # 退出 with 块时,如果操作成功会自动提交,如果发生异常则回滚并关闭 session

“`

sessionmaker 的参数说明:

  • autocommit=False:禁用自动提交。这意味着你需要手动调用 session.commit() 来保存更改。这是推荐的设置,因为它能更好地控制事务边界。
  • autoflush=False:禁用自动刷新。刷新是指 Session 将对象的变化同步到数据库,但尚未提交。禁用后,变化只会在查询或提交时刷新。
  • bind=engine:将 Session 绑定到特定的 Engine。

添加数据 (ORM)

通过创建模型对象,然后使用 Session 的 add() 方法将其添加到 Session 中,最后提交 Session 来保存到数据库。

“`python

使用 context manager 管理 session

with SessionLocal() as session:
# 创建 User 对象
new_user = User(name=’David’, age=40)
new_user2 = User(name=’Eve’, age=28)

# 将对象添加到 session
session.add(new_user)
session.add(new_user2)

# 创建 Post 对象,并关联作者
new_post1 = Post(title='My First Post', content='Hello SQLAlchemy!', author=new_user) # 直接通过 relationship 属性关联
new_post2 = Post(title='Another Post', content='More content...', author_id=new_user.id) # 或通过外键列关联

session.add_all([new_post1, new_post2]) # 添加多个对象

# 提交 session,将更改保存到数据库
session.commit()

print(f"\n添加用户 {new_user.name} (ID: {new_user.id}) 和 {new_user2.name} (ID: {new_user2.id})")
print(f"添加帖子 '{new_post1.title}' 和 '{new_post2.title}'")

# 注意:提交后,对象的 id 属性会被数据库生成的值填充

“`

查询数据 (ORM)

ORM 查询通常使用 Session 的 query() 方法,指定要查询的模型类。

“`python
with SessionLocal() as session:
# 查询所有 User 对象
users = session.query(User).all()
print(“\n所有用户 (ORM):”)
for user in users:
print(user) # 会调用 User.repr

# 按主键查询单个对象
user_by_id = session.query(User).get(1) # get 方法根据主键查询,如果不存在返回 None
if user_by_id:
    print(f"\n通过 ID 查询到用户: {user_by_id}")

# 使用 filter_by 进行简单条件查询 (使用关键字参数)
user_by_name = session.query(User).filter_by(name='David').first() # first() 获取第一个匹配项或 None
if user_by_name:
    print(f"\n通过 filter_by 查询到用户 David: {user_by_name}")

# 使用 filter 进行复杂条件查询 (使用表达式)
users_older_than_30 = session.query(User).filter(User.age > 30).all()
print("\n年龄大于 30 的用户 (ORM):")
for user in users_older_than_30:
    print(user)

# 使用 AND 连接多个条件
users_david_age_40 = session.query(User).filter(User.name == 'David', User.age == 40).first()
print(f"\n通过 AND 查询到用户 David (40岁): {users_david_age_40}")

# 使用 OR 连接多个条件 (需要导入 or_ 函数)
from sqlalchemy import or_
users_david_or_eve = session.query(User).filter(or_(User.name == 'David', User.name == 'Eve')).all()
print("\n通过 OR 查询到用户 David 或 Eve:")
for user in users_david_or_eve:
    print(user)

# 使用 ORDER BY
users_ordered = session.query(User).order_by(User.name.desc()).all() # desc() 降序
print("\n按姓名降序排序的用户:")
for user in users_ordered:
    print(user)

# 使用 LIMIT 和 OFFSET (分页)
users_paged = session.query(User).limit(2).offset(1).all() # 获取第2页的2条记录 (从索引1开始)
print("\n分页查询 (第2页,每页2条):")
for user in users_paged:
    print(user)

# 查询关联数据
all_posts = session.query(Post).all()
print("\n所有帖子及其作者:")
for post in all_posts:
    # 通过 relationship 属性访问关联的 User 对象
    print(f"帖子: '{post.title}', 作者: {post.author.name}")
    # 注意:访问 post.author 会触发一次新的数据库查询(惰性加载),这可能导致 N+1 查询问题

“`

关于 N+1 查询问题: 在上面的例子中,当我们遍历 all_posts 时,每次访问 post.author 都可能触发一次数据库查询来加载作者信息。如果有 N 篇帖子,就会有 1(查询帖子)+ N(查询作者)次数据库查询,这就是 N+1 问题。对于大量数据,这会严重影响性能。

SQLAlchemy 提供了解决方案,如使用 joinedloadselectinload 来进行关联数据的渴望加载 (Eager Loading),在查询帖子时就一并加载关联的作者数据。

“`python
from sqlalchemy.orm import joinedload

with SessionLocal() as session:
# 使用 joinedload 一次性加载帖子及其作者
posts_with_authors = session.query(Post).options(joinedload(Post.author)).all()
print(“\n所有帖子及其作者 (渴望加载):”)
for post in posts_with_authors:
# 现在访问 post.author 不会触发新的查询
print(f”帖子: ‘{post.title}’, 作者: {post.author.name}”)

# 对于一对多关系,selectinload 通常更高效
users_with_posts = session.query(User).options(selectinload(User.posts)).all()
print("\n所有用户及其帖子 (渴望加载):")
for user in users_with_posts:
    print(f"用户: {user.name}")
    for post in user.posts: # 访问 user.posts 不会触发新的查询
        print(f"  - 帖子: '{post.title}'")

“`

更新数据 (ORM)

更新数据非常简单:从 Session 中获取对象,修改其属性,然后提交 Session。

“`python
with SessionLocal() as session:
# 获取要更新的对象
user_to_update = session.query(User).filter_by(name=’David’).first()

if user_to_update:
    # 修改对象的属性
    user_to_update.age = 41
    user_to_update.name = 'David Lee' # 也可以修改其他属性

    # Session 会跟踪对象的修改
    # 提交 session,将更改保存到数据库
    session.commit()
    print(f"\n更新了用户 {user_to_update.name} 的年龄到 {user_to_update.age}")

    # 提交后,user_to_update 对象仍然可用,并且包含更新后的值
    # print(user_to_update)
else:
    print("\n未找到用户 David 进行更新")

“`

删除数据 (ORM)

从 Session 中获取要删除的对象,使用 Session 的 delete() 方法将其标记为删除,然后提交 Session。

“`python
with SessionLocal() as session:
# 获取要删除的对象
user_to_delete = session.query(User).filter_by(name=’Eve’).first()

if user_to_delete:
    # 将对象标记为删除
    session.delete(user_to_delete)

    # 提交 session,执行删除操作
    session.commit()
    print(f"\n删除了用户 {user_to_delete.name}")

    # 注意:提交后,user_to_delete 对象通常处于 detached 状态,不应再使用
else:
    print("\n未找到用户 Eve 进行删除")

“`

事务管理

无论是 Core 还是 ORM,事务管理都至关重要,它确保数据库操作的原子性、一致性、隔离性和持久性 (ACID)。

  • Core 的事务:

    • 使用 with engine.connect() as conn::这是一种推荐的方式,它会自动获取连接并在块结束时释放。默认情况下,如果块成功完成,它会尝试提交;如果发生异常,它会回滚。
    • 使用 with conn.begin()::如果需要更明确地控制事务边界,可以在连接对象上使用 begin() 上下文管理器。在 begin() 块内的所有操作都会被视为一个事务,成功完成时提交,异常时回滚。
    • 手动控制:你也可以通过 conn.begin(), conn.commit(), conn.rollback() 方法手动管理事务,但这不如上下文管理器安全,容易忘记提交或回滚。
  • ORM 的事务:

    • 使用 with SessionLocal() as session::这是推荐的 ORM 事务管理方式。Session 充当工作单元,with 块结束时会自动提交或回滚并关闭 Session。
    • 手动控制:你可以通过 session.commit(), session.rollback(), session.close() 方法手动管理 Session 和事务,但这同样容易出错。

重要提示: ORM 的 Session 是一个轻量级的对象,不应长时间持有或在多个请求/线程之间共享同一个 Session 实例。每次进行一系列相关的数据库操作时,都应该获取一个新的 Session 并在操作完成后关闭它(使用 with 语句是最佳实践)。

ORM 关系:一对多,多对多

关系是 ORM 的强大之处,它让你可以方便地在关联对象之间导航。我们已经在 User 和 Post 之间建立了一对多关系 (User.postsPost.author)。

SQLAlchemy 支持以下主要关系类型:

  1. 一对多 (One-to-Many): 一个对象关联多个其他对象。例如,一个用户有多篇帖子。
    • 在“一”的一方(User 模型)使用 relationship() 定义集合属性 (posts)。
    • 在“多”的一方(Post 模型)定义一个外键列 (author_id),并可以使用 relationship() 定义指向“一”的对象的属性 (author),通常配合 backrefback_populates 使用。
  2. 多对一 (Many-to-One): 多个对象关联同一个其他对象。这实际上是“一对多”从另一侧看。例如,多篇帖子关联同一个作者。
    • 在“多”的一方(Post 模型)定义外键列 (author_id) 和指向“一”的对象的属性 (author)。
    • 在“一”的一方(User 模型)使用 relationship() 定义集合属性 (posts),通常配合 backrefback_populates
  3. 多对多 (Many-to-Many): 多个对象关联多个其他对象,反之亦然。例如,学生与课程之间的关系(一个学生可以选多门课,一门课可以有多个学生)。这通常需要一个关联表 (Association Table) 来连接两个模型。
    • 定义一个独立的表来存储两个模型的主键作为外键。
    • 在两个模型中都使用 relationship(),并指定 secondary 参数为关联表。
  4. 一对一 (One-to-One): 一个对象最多关联一个其他对象,反之亦然。例如,一个用户有一个详细信息记录。
    • 在其中一个模型中定义外键列,并在该列上添加 unique=True 约束。
    • 在两个模型中都使用 relationship(),并在其中一侧或两侧添加 uselist=False 参数。

多对多关系示例 (学生与课程):

“`python
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship

1. 定义关联表 (没有对应的 ORM 模型类)

association_table = Table(
‘student_course_association’, Base.metadata, # 关联到 Base.metadata
Column(‘student_id’, Integer, ForeignKey(‘students.id’), primary_key=True),
Column(‘course_id’, Integer, ForeignKey(‘courses.id’), primary_key=True)
)

2. 定义 Student 模型

class Student(Base):
tablename = ‘students’
id = Column(Integer, primary_key=True)
name = Column(String)

# 定义与 Course 的多对多关系
courses = relationship(
    "Course",
    secondary=association_table, # 指定关联表
    back_populates="students" # 指向 Course 模型中的对应属性
)

3. 定义 Course 模型

class Course(Base):
tablename = ‘courses’
id = Column(Integer, primary_key=True)
title = Column(String)

# 定义与 Student 的多对多关系
students = relationship(
    "Student",
    secondary=association_table, # 指定关联表
    back_populates="courses" # 指向 Student 模型中的对应属性
)

创建表

Base.metadata.create_all(engine)

使用示例

with SessionLocal() as session:

s1 = Student(name=”Alice”)

s2 = Student(name=”Bob”)

c1 = Course(title=”Math”)

c2 = Course(title=”Physics”)

# 关联学生和课程

s1.courses.append(c1)

s1.courses.append(c2)

c2.students.append(s2) # 也可以从另一侧添加

session.add_all([s1, s2, c1, c2])

session.commit()

# 查询示例

retrieved_student = session.query(Student).filter_by(name=”Alice”).first()

print(f”\n学生 {retrieved_student.name} 选修的课程:”)

for course in retrieved_student.courses:

print(f” – {course.title}”)

“`

什么时候使用 Core,什么时候使用 ORM?

  • 优先使用 ORM: 对于大部分常见的 CRUD 操作、涉及到模型关系和业务逻辑的场景,ORM 提供了更高的抽象度和开发效率。它能让你用面向对象的方式思考和操作数据。
  • 使用 Core:
    • 批量操作: 对于需要高性能批量插入、更新、删除大量数据的场景,直接使用 Core 的 insert(), update(), delete() 表达式通常比 ORM 更快,因为 ORM 需要加载对象、跟踪状态等开销。
    • 复杂的、非映射的查询: 当你需要执行非常复杂、涉及大量联接、子查询、数据库特定函数等,并且结果不直接映射到现有模型的查询时,使用 Core 构建 SQL 表达式可能更直接和灵活。
    • Schema 操作: Core 的 MetaData 提供了创建、删除表的接口,以及更底层的操作。
    • 性能瓶颈: 如果发现 ORM 在某个特定查询或操作上存在性能瓶颈,可以尝试使用 Core 来实现相同的逻辑,通常能获得更细粒度的控制。
    • 仅需要 SQL 抽象: 如果你只是想利用 SQLAlchemy 的 SQL 构建能力和数据库方言抽象,但不需要 ORM 的对象映射功能,直接使用 Core 即可。

SQLAlchemy 的强大之处在于你可以根据具体需求在两个层面之间切换或混合使用。

进阶话题预览

本指南只覆盖了 SQLAlchemy 的基础知识。SQLAlchemy 还有许多更高级的功能和概念值得深入学习:

  • Alembic: SQLAlchemy 官方推荐的数据库迁移工具,用于管理数据库 Schema 的版本变更。
  • 连接池配置: 优化数据库连接的创建、复用和管理,对于高并发应用至关重要。
  • 加载策略: 除了惰性加载和渴望加载,还有其他加载策略(如延迟加载、无加载)以及如何选择最适合的策略来优化性能。
  • 会话管理模式: 在 Web 应用等不同场景下,如何正确地创建、使用和关闭 Session。
  • 复合主键、联合索引: 如何在模型中定义更复杂的数据库结构。
  • 继承映射: 如何将面向对象的继承关系映射到数据库表(单表继承、类表继承等)。
  • 事件系统: 在对象生命周期(如加载前、保存前)或 Session/Engine 事件发生时执行自定义逻辑。
  • 自定义类型和函数: 如何映射数据库特有的数据类型或函数。
  • 错误处理: 了解 SQLAlchemy 可能抛出的异常以及如何处理。

最佳实践与常见陷阱

  • 正确管理 Session: 始终使用 with SessionLocal() as session: 来确保 Session 被正确关闭和事务被管理。不要在函数之间传递同一个 Session 实例(除非你明确知道自己在做什么,比如在请求上下文管理器中)。
  • 理解惰性加载 (Lazy Loading): 访问关系属性时可能触发新的查询。意识到这一点,并在需要时使用渴望加载来避免 N+1 问题。
  • 使用 Alembic 进行数据库迁移: 手动管理数据库 Schema 变更容易出错。Alembic 能帮助你以结构化的方式定义和应用数据库变更。
  • 不要在模型定义中包含业务逻辑: 模型类应该主要关注数据结构和映射关系。业务逻辑应该放在单独的服务层或管理器中。
  • 参数化查询: SQLAlchemy Core 和 ORM 在构建查询时都会自动处理参数化,这能有效防止 SQL 注入。始终使用 SQLAlchemy 提供的接口来传递值,而不是手动拼接字符串。
  • 日志记录: 配置 SQLAlchemy 的日志输出(例如 echo=Truecreate_engine 中)能帮助你查看实际执行的 SQL 语句,这对于调试和性能优化非常有帮助。

总结

SQLAlchemy 是一个功能强大、灵活且成熟的 Python 数据库工具包。它由底层的 Core 和高层的 ORM 两部分组成。Core 提供了强大的 SQL 表达式构建能力,适用于需要精细控制 SQL 或进行批量操作的场景。ORM 则通过对象映射极大地简化了数据库操作,让你可以用面向对象的方式进行 CRUD 和管理对象关系。

本指南为你介绍了 SQLAlchemy 的安装、连接数据库、Core 的基本用法(定义表、增删改查)以及 ORM 的基本用法(定义模型、创建 Session、增删改查、关系定义)。理解 Core 是理解 ORM 基础的重要一步。掌握 ORM 的基本操作和关系定义是使用 SQLAlchemy 进行高效应用开发的必备技能。

这只是 SQLAlchemy 庞大功能的冰山一角。随着你的使用经验增加,你会发现更多强大的特性。

下一步:

  • 尝试运行本指南中的代码示例,亲手实践。
  • 阅读 SQLAlchemy 官方文档,它是最权威和详细的资源。
  • 学习 Alembic,将数据库迁移整合到你的开发流程中。
  • 探索更高级的加载策略和查询技术。
  • 在实际项目中应用 SQLAlchemy,解决实际问题。

祝你在 SQLAlchemy 的学习之旅中取得成功!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部