为面向对象编程思维而生的数据库优雅访问:SQLAlchemy
本文章仅提供学习,切勿将其用于不法手段!
——从“数据库小白”到“ORM 老司机”的进阶之路
前言:为什么学 SQLAlchemy?
你有没有过这种经历:写 Python 操作数据库时,一会儿拼 SQL 字符串(比如 SELECT * FROM user WHERE id=1),一会儿处理各种数据库差异(MySQL 用 AUTO_INCREMENT,PostgreSQL 用 SERIAL),改个表结构要手动改 N 处代码……累得头都大了?
这时候,SQLAlchemy 就像个“数据库管家”——它帮你把 Python 对象和数据库表“绑”在一起(这叫 ORM,对象关系映射),让你用写 Python 类的方式操作数据库,不用再和 SQL 字符串死磕。而且它支持几乎所有主流数据库(MySQL、PostgreSQL、SQLite 等),切换数据库时几乎不用改业务代码!
本教程会从“最基础的安装”开始,用大白话+生活例子,一步步带你搞懂 SQLAlchemy 的核心玩法,最后还能玩点高级操作。放心,就算你是数据库新手,也能看懂!
第一章:先搭个“实验室”——安装与环境准备
1.1 安装 SQLAlchemy
首先,你得把 SQLAlchemy 装到电脑里。打开终端(Windows 叫“命令提示符”或“PowerShell”),输入:
pip install sqlalchemy
如果要用 MySQL,还得装个“驱动”(相当于翻译官,让 SQLAlchemy 能和 MySQL 说话):
pip install pymysql # MySQL 驱动(其他数据库用不同驱动,比如 psycopg2 对应 PostgreSQL)
1.2 准备一个“测试数据库”
为了安全,咱们先用 SQLite(文件型数据库,不用单独装服务,适合练手)。新建一个文件夹(比如叫 sqlalchemy-tutorial),在里面放个空文件 test.db(这就是我们的“数据库文件”)。
如果用 MySQL,需要先启动 MySQL 服务,然后创建一个测试库(比如叫 test_db):
CREATE DATABASE test_db;
1.3 验证安装:连个数据库试试
先写段代码,看看能不能连上数据库。新建 demo.py:
from sqlalchemy import create_engine
# 连接 SQLite(注意:sqlite:/// 后面跟的是文件路径,三个斜杠表示相对路径)
engine = create_engine("sqlite:///test.db")
# 测试连接:执行一个简单的 SQL(创建表)
with engine.connect() as conn:
conn.execute(text("CREATE TABLE IF NOT EXISTS test (id INT, name TEXT)"))
print("数据库连接成功!表 test 已创建(如果不存在的话)")
运行 python demo.py,如果没报错且打印“数据库连接成功”,说明环境 OK 了!
本章重点:
- SQLAlchemy 安装用
pip,MySQL 需额外装驱动(如pymysql)。 create_engine是“连接器”,负责和数据库建立连接(类似“电话线”)。
第二章:ORM 入门——用 Python 类“变”出数据库表
2.1 什么是 ORM?“对象”和“表”怎么绑一起?
假设你有个“用户”的概念:每个用户有 id(编号)、name(姓名)、age(年龄)。用普通方式存数据库,你得手动建表、插数据;但用 ORM,你可以定义一个 Python 类 User,然后 SQLAlchemy 自动帮你生成对应的数据库表!
类比:类是“设计图纸”,表是“按图纸盖的房子”。你改图纸(类),房子(表)的结构也会跟着变(当然需要迁移工具,后面会讲)。
2.2 定义第一个 ORM 类:User 表
修改 demo.py,引入 ORM 需要的工具:
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# 第一步:创建“基类”(所有 ORM 类的爸爸)
Base = declarative_base()
# 第二步:定义 User 类(对应数据库中的 user 表)
class User(Base):
# 表名(如果不写,默认是类名小写)
__tablename__ = "user"
# 字段定义(列):类型要和数据库对应
id = Column(Integer, primary_key=True, autoincrement=True) # 主键,自增
name = Column(String(50), nullable=False) # 字符串,最长50,不能为空
age = Column(Integer) # 整数,可以为空
def __repr__(self):
return f"<User(id={self.id}, name='{self.name}', age={self.age})>" # 方便打印看结果
2.3 让类“变成”表:创建表结构
定义了类还不够,得告诉数据库“按这个类建表”。用 Base.metadata.create_all(engine):
# 接上面的代码
engine = create_engine("sqlite:///test.db")
# 第三步:根据 Base 的所有子类(比如 User)创建表(如果不存在)
Base.metadata.create_all(engine)
print("User 表已创建!")
运行后,打开 test.db(可以用 SQLite 浏览器工具查看),会发现多了个 user 表,字段和 User 类定义的一致!
本章重点:
declarative_base()生成 ORM 基类,所有表类都要继承它。__tablename__指定表名,Column定义字段(类型、约束如primary_key)。Base.metadata.create_all(engine)是“建表指令”,只在表不存在时创建。
第三章:增删改查(CRUD)——用 Python 代码操作数据库
表建好了,接下来就是最常用的“增删改查”。SQLAlchemy 用 Session(会话)管理操作,可以理解为“临时工作台”:你把要做的操作(增删改)放到工作台上,最后一起提交给数据库。
3.1 先搞个“工作台”:创建 Session
在之前的代码基础上,加一段创建 Session 的配置:
# 接前面的代码(engine 已定义)
# 创建 Session 工厂(类似“工作台模板”)
Session = sessionmaker(bind=engine)
# 每次操作前,生成一个具体的 Session(工作台)
session = Session()
3.2 增加(Create):插入新用户
往表里加数据,就像“new 一个对象,然后告诉 Session 保存它”:
# 新增单个用户
user1 = User(name="小明", age=20)
session.add(user1) # 把 user1 放到“待提交区”
# 新增多个用户(批量添加)
user2 = User(name="小红", age=22)
user3 = User(name="小刚", age=21)
session.add_all([user2, user3]) # 批量添加到“待提交区”
# 提交操作(把“待提交区”的内容真正写入数据库)
session.commit()
print("新增用户成功!当前用户:")
print(session.query(User).all()) # 查询所有用户看看
运行后,数据库里会有 3 条记录。session.commit() 是关键——没提交的话,数据只在“内存”里,没进数据库!
3.3 查询(Read):找你想要的数据
查数据是最高频的操作,SQLAlchemy 提供了多种查询方式,像“点菜”一样灵活:
(1)查所有:query().all()
all_users = session.query(User).all() # 返回所有 User 对象的列表
print(all_users) # [<User(id=1, name='小明', age=20)>, ...]
(2)查单个:query().first() 或 get()
# 按条件查第一个匹配的(比如 name='小明')
user = session.query(User).filter_by(name="小明").first()
print(user) # <User(id=1, name='小明', age=20)>
# 按主键查(id=1)
user = session.get(User, 1) # 更高效,直接定位主键
print(user)
(3)复杂条件:filter()(比 filter_by 更强大)
filter_by 只能传“键值对”(比如 name="小明"),而 filter 可以用表达式(比如 age > 20):
# 查年龄大于20的用户
users = session.query(User).filter(User.age > 20).all()
print(users) # [<User(id=2, name='小红', age=22)>, <User(id=3, name='小刚', age=21)>]
# 多条件(年龄>20 且 姓名含“红”)
users = session.query(User).filter(User.age > 20, User.name.contains("红")).all()
print(users) # [<User(id=2, name='小红', age=22)>]
3.4 修改(Update):改用户信息
改数据有两种思路:先查出来改,再提交 或 直接批量改。
方式1:查出来改(适合改单条)
# 先找到要改的用户
user = session.get(User, 1)
if user:
user.age = 21 # 直接改对象的属性(Python 对象的特性)
session.commit() # 提交修改
print("修改成功!")
方式2:批量改(适合改多条)
# 把所有年龄=21的用户,年龄改成22
session.query(User).filter(User.age == 21).update({"age": 22})
session.commit()
print("批量修改成功!")
3.5 删除(Delete):删掉不需要的数据
和改数据类似,也是“先查后删”或“直接批量删”。
方式1:查出来删(适合删单条)
user = session.get(User, 3)
if user:
session.delete(user) # 标记删除
session.commit() # 提交删除
print("删除成功!")
方式2:批量删
# 删掉所有年龄>22的用户(这里示例数据可能没有,所以不会删任何东西)
session.query(User).filter(User.age > 22).delete()
session.commit()
print("批量删除成功!")
本章重点:
Session是操作数据库的“工作台”,add/add_all增,query查,delete删,commit提交。- 查数据时,
filter_by简单条件,filter复杂条件;all()返回列表,first()返回单个对象。
第四章:关系映射——表和表之间怎么“关联”?
实际业务中,表很少孤立存在。比如“用户”和“订单”是一对多(一个用户多个订单),“用户”和“角色”是多对多(一个用户多个角色,一个角色多个用户)。SQLAlchemy 能轻松搞定这些关系!
4.1 一对多:用户和订单(一个用户→多个订单)
假设用户表(user)和订单表(order),一个用户可以有多个订单,每个订单属于一个用户。
步骤1:定义 Order 类,添加外键
from sqlalchemy import ForeignKey
class Order(Base):
__tablename__ = "order"
id = Column(Integer, primary_key=True, autoincrement=True)
order_no = Column(String(50), nullable=False) # 订单号
user_id = Column(Integer, ForeignKey("user.id")) # 外键:关联到 user 表的 id 字段
# 关键:定义“多”的一方(Order)如何访问“一”的一方(User)
user = relationship("User", back_populates="orders")
# 回到 User 类,添加反向引用(一的一方如何访问多的一方)
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(50), nullable=False)
age = Column(Integer)
# 新增:一个用户有多个订单(relationship 定义关系)
orders = relationship("Order", back_populates="user")
def __repr__(self):
return f"<User(id={self.id}, name='{self.name}', age={self.age})>"
步骤2:重新建表(因为加了新表 order)
Base.metadata.create_all(engine) # 会同时检查 user 和 order 表,不存在则创建
步骤3:用关系操作数据
现在可以通过用户直接访问他的订单,或通过订单访问所属用户:
# 新增用户和订单(自动关联)
user = User(name="小李", age=25)
order1 = Order(order_no="ORDER001", user=user) # 直接把 user 对象传给 user_id
order2 = Order(order_no="ORDER002", user=user)
session.add_all([user, order1, order2])
session.commit()
# 查用户的订单(正向访问:user → orders)
print(user.orders) # [<Order(id=1, order_no='ORDER001', user_id=4)>, ...]
# 查订单的用户(反向访问:order → user)
print(order1.user) # <User(id=4, name='小李', age=25)>
4.2 多对多:用户和角色(用户←→角色)
多对多需要一个“中间表”来记录关联关系(比如 user_role 表,存 user_id 和 role_id)。
步骤1:定义 Role 类和中间表
from sqlalchemy import Table
# 中间表(不需要 ORM 类,直接用 Table 定义)
user_role = Table(
"user_role", # 表名
Base.metadata,
Column("user_id", Integer, ForeignKey("user.id"), primary_key=True),
Column("role_id", Integer, ForeignKey("role.id"), primary_key=True)
)
class Role(Base):
__tablename__ = "role"
id = Column(Integer, primary_key=True, autoincrement=True)
role_name = Column(String(50), nullable=False)
# 多对多关系:一个角色被多个用户拥有
users = relationship("User", secondary=user_role, back_populates="roles")
# 回到 User 类,添加多对多关系
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(50), nullable=False)
age = Column(Integer)
orders = relationship("Order", back_populates="user")
roles = relationship("Role", secondary=user_role, back_populates="users") # secondary 指定中间表
步骤2:重新建表(新增 role 和 user_role 表)
Base.metadata.create_all(engine)
步骤3:用多对多关系操作
# 新增角色和用户,并关联
role_admin = Role(role_name="管理员")
role_user = Role(role_name="普通用户")
user = User(name="小王", age=23)
# 给用户分配角色(直接操作 roles 列表)
user.roles.extend([role_admin, role_user])
session.add_all([role_admin, role_user, user])
session.commit()
# 查用户的角色(正向访问)
print(user.roles) # [<Role(id=1, role_name='管理员')>, <Role(id=2, role_name='普通用户')>]
# 查角色的用户(反向访问)
print(role_admin.users) # [<User(id=5, name='小王', age=23)>]
本章重点:
- 一对多:在“多”的一方加外键,
relationship定义双向访问(back_populates)。 - 多对多:需要中间表,
relationship用secondary参数指定中间表。
第五章:高级玩法——让操作更“丝滑”
5.1 事务:要么全成功,要么全失败
比如转账:A 扣钱和 B 加钱必须同时成功或同时失败。SQLAlchemy 的 Session 自带事务,默认 commit 时提交,rollback 时回滚。
try:
# 模拟转账:A 给用户1加钱,用户1给用户2转钱(故意写错,测试回滚)
user_a = User(name="A", age=30)
user_b = User(name="B", age=28)
session.add_all([user_a, user_b])
session.flush() # 预提交,获取 id(但不真正写入数据库)
# 假设这里有个错误(比如除零)
raise ValueError("模拟出错!")
session.commit() # 如果前面出错,这行不会执行
except Exception as e:
print(f"出错了:{e},回滚事务")
session.rollback() # 回滚到操作前的状态
5.2 原生 SQL:复杂查询用它
ORM 虽好,但遇到特别复杂的 SQL(比如窗口函数、多表联查优化),直接写原生 SQL 更直观。用 text() 包裹 SQL 字符串:
from sqlalchemy import text
# 执行原生 SELECT
result = session.execute(text("SELECT * FROM user WHERE age > :age"), {"age": 20})
for row in result:
print(row) # 每行是一个元组(id, name, age)
# 执行原生 INSERT(注意防注入,用参数绑定)
session.execute(text("INSERT INTO user (name, age) VALUES (:name, :age)"), {"name": "小张", "age": 24})
session.commit()
5.3 性能优化:避免 N+1 查询问题
当查询一个用户的所有订单时,如果直接循环 user.orders,可能会触发多次查询(N+1 问题:1 次查用户 + N 次查订单)。用 joinedload 预加载关联数据:
from sqlalchemy.orm import joinedload
# 一次性查出用户和他的所有订单(只发 1 次 JOIN 查询)
users = session.query(User).options(joinedload(User.orders)).all()
for user in users:
print(user.orders) # 不会再触发额外查询
本章重点:
- 事务用
try...except...rollback保证原子性。 - 复杂 SQL 用
text()写原生语句(注意参数绑定防注入)。 - N+1 查询用
joinedload预加载关联数据,提升性能。
总结:SQLAlchemy 到底强在哪?
到这里,你已经掌握了 SQLAlchemy 的核心技能:用 ORM 类映射表、CRUD 操作、关系映射、事务和性能优化。它的优势总结起来就三点:
- 省代码:不用手写 SQL,用 Python 类和方法就能操作数据库。
- 跨数据库:换数据库(比如从 SQLite 切 MySQL)只需改连接字符串,业务代码不动。
- 灵活扩展:支持原生 SQL、复杂关系、事务等高级功能,能满足大部分业务需求。
下一步可以研究 Alembic(SQLAlchemy 的迁移工具),它能帮你优雅地升级/降级数据库表结构(比如加字段、改类型),彻底告别手动改 SQL 的痛苦!
遇到问题别慌,SQLAlchemy 官方文档(https://docs.sqlalchemy.org)是你的“终极字典”,多翻多练,你也能成为 ORM 老司机!
注:本文仅用于教育目的,实际渗透测试必须获得合法授权。未经授权的黑客行为是违法的。
SQLAlchemy入门与实战指南

925

被折叠的 条评论
为什么被折叠?



