SQLAlchemy 是一个功能强大的 Python SQL 工具包和对象关系映射(ORM)框架,旨在提供高效、灵活且便于扩展的数据库交互解决方案。它支持多种数据库,并通过其核心(Core)和 ORM 两个层次为开发者提供不同的抽象级别。
本指南将主要聚焦于 ORM 层,同时涵盖 Core 的一些关键概念。
在深入 ORM 之前,理解 SQLAlchemy 的核心组件是至关重要的。这些组件构成了与数据库交互的基础。
Engine 是 SQLAlchemy 与数据库之间的接口,负责管理数据库连接和执行 SQL 语句。
from sqlalchemy import create_engine
DATABASE_URL = "sqlite:///./example.db"
engine = create_engine(
DATABASE_URL,
connect_args={"check_same_thread": False} # SQLite特有参数
)
sqlite:///./example.db
postgresql://user:password@localhost:5432/mydatabase
mysql+pymysql://user:password@localhost:3306/mydatabase
check_same_thread=False
:允许在多个线程中使用同一个 SQLite 连接。默认情况下,SQLite 不允许跨线程使用连接,这是为了防止潜在的线程安全问题。engine = create_engine(
"sqlite:///./example.db",
connect_args={"check_same_thread": False}
)
这行代码创建了一个与 example.db
SQLite 数据库的连接,并允许跨线程使用该连接。这在多线程应用中非常有用,例如 Web 服务器处理多个并发请求时。
MetaData 是 SQLAlchemy 用于存储关于数据库结构信息的对象。它包含了所有表、列、约束等的定义。
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
MetaData
对象。MetaData
自动生成创建表、添加列等 SQL 语句。Table 和 Column 是定义数据库表和其列的基础组件。
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime
Base = declarative_base()
class ToDo(Base):
__tablename__ = 'todo'
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True, nullable=False)
description = Column(String, nullable=True)
completed = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
Integer
, String
, Boolean
, DateTime
等,定义列的数据类型。SQLAlchemy 的 ORM 层允许你将数据库表映射为 Python 类,并以面向对象的方式进行数据库操作。
声明式基础类是定义 ORM 模型的推荐方式。通过继承 Base
,你可以定义与数据库表对应的 Python 类。
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
Base
,模型类的定义更加直观和简洁。MetaData
对象,便于统一管理。模型类代表数据库中的表,每个实例代表表中的一行。
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from datetime import datetime
class ToDo(Base):
__tablename__ = 'todo'
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True, nullable=False)
description = Column(String, nullable=True)
completed = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 如果有外键关系,可以定义 relationship
# owner_id = Column(Integer, ForeignKey('users.id'))
# owner = relationship("User", back_populates="todos")
Base
:模型类必须继承自声明式基础类 Base
。__tablename__
:指定对应的数据库表名。Column
类定义表中的列及其属性。relationship
定义表之间的关联关系。关系(Relationships)是 SQLAlchemy ORM 的核心功能,允许你在不同的数据库表之间建立关联,从而以面向对象的方式进行复杂的数据操作。主要关系类型包括一对多(One-to-Many)和多对多(Many-to-Many)。
场景:一个用户可以拥有多个待办事项。
实现步骤:
ToDo
)中定义指向父表(User
)的外键。relationship
定义子对象的集合。relationship
定义父对象的单一引用。示例代码:
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True, nullable=False)
# 一对多关系:一个用户有多个待办事项
todos = relationship("ToDo", back_populates="owner", cascade="all, delete-orphan")
class ToDo(Base):
__tablename__ = 'todo'
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True, nullable=False)
owner_id = Column(Integer, ForeignKey('users.id'))
# 反向关系:一个待办事项属于一个用户
owner = relationship("User", back_populates="todos")
关键参数:
relationship("ToDo", back_populates="owner")
:
"ToDo"
:关联的模型类名。back_populates="owner"
:在 ToDo
模型中对应的关系属性,确保双向同步。ForeignKey('users.id')
:
User
模型的 id
字段。使用示例:
# 创建用户和待办事项
user = User(username="john_doe")
todo1 = ToDo(title="Buy groceries", owner=user)
todo2 = ToDo(title="Write report", owner=user)
session.add(user)
session.commit()
# 查询用户的待办事项
retrieved_user = session.query(User).filter_by(username="john_doe").first()
print(retrieved_user.todos) # 输出: [, ]
场景:多个学生可以选修多门课程,每门课程也可以被多个学生选修。
实现步骤:
relationship
并通过 secondary
参数指定关联表。示例代码:
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
# 关联表
student_course = Table('student_course', Base.metadata,
Column('student_id', Integer, ForeignKey('students.id'), primary_key=True),
Column('course_id', Integer, ForeignKey('courses.id'), primary_key=True)
)
class Student(Base):
__tablename__ = 'students'
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
# 多对多关系
courses = relationship("Course", secondary=student_course, back_populates="students")
class Course(Base):
__tablename__ = 'courses'
id = Column(Integer, primary_key=True, index=True)
title = Column(String, nullable=False)
# 多对多关系
students = relationship("Student", secondary=student_course, back_populates="courses")
关键参数:
secondary=student_course
:
back_populates="students"
和 back_populates="courses"
:
使用示例:
# 创建学生和课程
student1 = Student(name="Alice")
student2 = Student(name="Bob")
course1 = Course(title="Math")
course2 = Course(title="Science")
# 建立关联
student1.courses.append(course1)
student1.courses.append(course2)
student2.courses.append(course1)
session.add_all([student1, student2, course1, course2])
session.commit()
# 查询课程的学生
retrieved_course = session.query(Course).filter_by(title="Math").first()
print(retrieved_course.students) # 输出: [, ]
relationship
:
back_populates
:定义双向关系,确保两个模型之间的关联同步更新。secondary
:在多对多关系中指定关联表。cascade
:控制父对象操作对子对象的影响(如 all, delete-orphan
确保删除父对象时自动删除关联的子对象)。ForeignKey
:
back_populates
确保关系的双向同步,避免数据不一致。cascade
参数,自动管理关联对象的生命周期,减少手动操作的繁琐。joinedload
)优化查询性能。预加载示例:
from sqlalchemy.orm import joinedload
# 一对多关系预加载
users = session.query(User).options(joinedload(User.todos)).all()
# 多对多关系预加载
students = session.query(Student).options(joinedload(Student.courses)).all()
Session 是 SQLAlchemy ORM 与数据库交互的主要接口,负责管理对象的持久化和事务。
创建一个会话工厂,用于生成会话对象。
from sqlalchemy.orm import sessionmaker
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
autocommit=False
:
False
False
,需要手动调用 session.commit()
来提交事务。这样可以更好地控制事务边界,确保数据一致性。autoflush=False
:
True
bind=engine
:
通过会话对象进行数据库操作,并管理事务的提交与回滚。
def get_db():
"""
依赖函数,用于获取数据库会话。
"""
db = SessionLocal()
try:
yield db # 将会话对象db提供给依赖他的路由处理函数。当路由处理完成后,生成器会回复执行finally块。
finally:
db.close()# 确保即使在请求处理过程中发生异常,也能执行finally块,关闭数据库会话。
get_db
函数是一个生成器函数,它通过依赖注入(Dependency Injection)的方式,为FastAPI的路由处理函数提供一个数据库会话(Session)。这个函数确保每个HTTP请求都有一个独立的数据库会话,并在请求完成后自动关闭会话,避免资源泄露和潜在的连接问题。
依赖注入是一种设计模式,允许你再函数或类中生命所需的依赖性,而不需要在函数内部创建这些依赖项。FastAPI强大地支持这种模式,通过Depends函数来实现。
yield
yield
语句的函数成为生成器函数。调用这样的函数会返回一个生成器对象,而不是立即执行函数体。yield
语句返回多个值,每次迭代时提供一个新的值。示例
def simple_generator():
yield "Hello"
yield "World"
gen = simple_generator()
print(next(gen)) # 输出: Hello
print(next(gen)) # 输出: World
在 FastAPI 中,通过 Depends
注入会话对象,确保每个请求使用独立的会话。
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from pydantic import BaseModel
app = FastAPI()
# Pydantic模型
class ToDoCreate(BaseModel):
title: str
description: Optional[str] = None
class ToDoOut(BaseModel):
id: int
title: str
description: Optional[str] = None
completed: bool
created_at: datetime
updated_at: datetime
class Config:
orm_mode = True
# API端点
@app.post('/todo', response_model=ToDoOut)
def create_todo(todo: ToDoCreate, db: Session = Depends(get_db)):
new_todo = ToDo(title=todo.title, description=todo.description)
db.add(new_todo)
db.commit()
db.refresh(new_todo)
return new_todo
@app.get('/todo', response_model=List[ToDoOut])
def get_todos(db: Session = Depends(get_db)):
todos = db.query(ToDo).all()
return todos
db.add(new_todo)
:将新对象添加到会话。db.commit()
:提交事务,将更改保存到数据库。db.refresh(new_todo)
:刷新对象状态,从数据库获取最新数据。finally
块中自动关闭,确保数据库连接被释放。session.commit()
提交事务。session.rollback()
回滚事务,以恢复到事务开始前的状态。使用 SQLAlchemy ORM 进行数据库查询是其核心功能之一。理解如何构建和优化查询是高效使用 SQLAlchemy 的关键。
todos = db.query(ToDo).all()
ToDo
表中的所有记录。ToDo
实例的列表。todo = db.query(ToDo).filter(ToDo.id == todo_id).first()
id
获取特定的待办事项。ToDo
实例,如果不存在则返回 None
。completed_todos = db.query(ToDo).filter(ToDo.completed == True).all()
completed=True
的 ToDo
实例的列表。from sqlalchemy import and_
filtered_todos = db.query(ToDo).filter(
and_(
ToDo.completed == False,
ToDo.title.contains("urgent")
)
).all()
ToDo
实例的列表。在涉及多个表的查询中,联接(Join)是必不可少的。SQLAlchemy 支持多种联接方式,如内联接、左外联接等。
todos_with_users = db.query(ToDo, User).join(User).filter(User.username == 'john').all()
ToDo
和 User
实例的元组列表。from sqlalchemy.orm import aliased
user_alias = aliased(User)
todos_with_optional_users = db.query(ToDo, user_alias).outerjoin(user_alias).all()
ToDo
和 User
(可能为 None
)实例的元组列表。聚合函数用于对查询结果进行统计,如计数、求和、平均值等。
from sqlalchemy import func
todo_count = db.query(func.count(ToDo.id)).scalar()
ToDo
表中的记录数量。假设 ToDo
表中有一个 priority
列:
total_priority = db.query(func.sum(ToDo.priority)).scalar()
priority
总和。None
,如果没有记录则返回 None
。from sqlalchemy import func
todos_grouped_by_status = db.query(ToDo.completed, func.count(ToDo.id)).group_by(ToDo.completed).all()
completed
状态和对应计数的元组列表。懒加载是 ORM 的默认行为,关联对象在访问时才加载。
todos = db.query(ToDo).all()
for todo in todos:
print(todo.owner.username) # 触发懒加载
预加载在主查询时加载关联对象,减少查询次数。
from sqlalchemy.orm import joinedload
todos = db.query(ToDo).options(joinedload(ToDo.owner)).all()
for todo in todos:
print(todo.owner.username) # 已经加载,不会触发额外查询
subqueryload
另一种预加载方式,适用于复杂的嵌套关联。
from sqlalchemy.orm import subqueryload
todos = db.query(ToDo).options(subqueryload(ToDo.owner)).all()
for todo in todos:
print(todo.owner.username)
混合属性允许定义既可以作为对象属性访问,又可以用于查询表达式的属性。
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import func
class ToDo(Base):
__tablename__ = 'todo'
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True, nullable=False)
completed = Column(Boolean, default=False)
@hybrid_property
def title_length(self):
return len(self.title)
@title_length.expression
def title_length(cls):
return func.length(cls.title)
# 访问属性
todo = db.query(ToDo).first()
print(todo.title_length)
# 用于查询
long_titles = db.query(ToDo).filter(ToDo.title_length > 10).all()
通过使用不同的映射选项,定制对象属性与数据库列之间的映射关系。
为列设置别名,便于使用不同的名称访问数据库列。
class ToDo(Base):
__tablename__ = 'todo'
id = Column(Integer, primary_key=True)
title = Column("task_title", String)
组合多个列为一个属性。
from sqlalchemy.ext.hybrid import hybrid_property
class ToDo(Base):
__tablename__ = 'todo'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
@hybrid_property
def full_name(self):
return f"{self.first_name} {self.last_name}"
在开发过程中,数据库模式常常需要变更。使用迁移工具可以有效地管理这些变更,确保数据库结构与代码模型同步。
Alembic 是 SQLAlchemy 的官方数据库迁移工具,允许你轻松地管理数据库模式的变更。它支持生成和应用迁移脚本,确保数据库结构的演进与代码同步。
在项目根目录下初始化 Alembic 配置:
alembic init alembic
这将创建一个 alembic
目录,包含迁移脚本和配置文件。
alembic.ini
编辑 alembic.ini
文件,设置数据库连接 URL:
# alembic.ini
[alembic]
script_location = alembic
sqlalchemy.url = sqlite:///./example.db # 根据实际数据库URL修改
env.py
确保 Alembic 能找到你的模型定义。编辑 alembic/env.py
,导入你的 Base
。
# alembic/env.py
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
import sys
import os
sys.path.append(os.path.abspath('.'))
from your_module import Base # 替换为实际的模块路径
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
fileConfig(config.config_file_name)
# add your model's MetaData object here
target_metadata = Base.metadata
def run_migrations_offline():
"""Run migrations in 'offline' mode."""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode."""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
在每次模型变更后,生成迁移脚本:
alembic revision --autogenerate -m "描述迁移内容"
--autogenerate
:自动检测模型变更并生成迁移脚本。-m "描述"
:为迁移脚本添加描述信息。假设你新增了 User
模型:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True, nullable=False)
email = Column(String, unique=True, index=True, nullable=False)
生成迁移脚本:
alembic revision --autogenerate -m "新增用户模型"
生成的迁移脚本将包含创建 users
表的 SQL 语句。
将迁移脚本应用到数据库:
alembic upgrade head
如果需要回滚到上一个迁移版本:
alembic downgrade -1