Flask-SQLAlchemy 详细使用指南与代码实例
Flask-SQLAlchemy 是 Flask 框架的一个扩展,它简化了 SQLAlchemy 在 Flask 应用中的集成与使用。SQLAlchemy 是 Python 中最强大的 ORM(对象关系映射)工具之一,而 Flask-SQLAlchemy 则在此基础上提供了更简洁的接口和与 Flask 的深度集成。本文将详细介绍其使用方法,包括环境搭建、模型定义、CRUD 操作、查询进阶等核心内容。
一、环境准备
1.1 安装依赖
首先需要安装 Flask 和 Flask-SQLAlchemy,以及数据库驱动(以 MySQL 为例):
# 安装核心依赖
pip install flask flask-sqlalchemy
# 安装 MySQL 驱动(根据数据库选择)
pip install pymysql # MySQL 驱动
# 如需 SQLite,无需额外驱动(Python 内置)
# 如需 PostgreSQL,安装 psycopg2: pip install psycopg2-binary
1.2 基本配置
在 Flask 应用中,需要通过配置项指定数据库连接信息和 SQLAlchemy 相关参数。推荐使用配置类的方式管理配置:
# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
# 初始化 SQLAlchemy 实例(暂不绑定 app)
db = SQLAlchemy()
class Config:
"""基础配置类"""
# 数据库连接 URI(MySQL 示例)
# 格式:mysql+pymysql://用户名:密码@主机:端口/数据库名?字符集参数
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:sasa@localhost:3306/flask_demo?charset=utf8mb4"
# 动态追踪修改设置,如未设置会报警告,不建议开启
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 显示 SQL 语句(开发环境调试用)
SQLALCHEMY_ECHO = False # 生产环境设为 False
# 数据库连接池大小
SQLALCHEMY_ENGINE_OPTIONS = {
"pool_size": 10, # 连接池大小
"max_overflow": 20, # 超出连接池大小时的最大连接数
"pool_recycle": 300, # 连接超时时间(秒)
}
def create_app():
"""应用工厂函数"""
app = Flask(__name__)
# 加载配置
app.config.from_object(Config)
# 初始化 app 与 SQLAlchemy
db.init_app(app)
# 注册蓝图(后续会用到)
from app.routes import main_bp
app.register_blueprint(main_bp)
# 创建数据库表(首次运行时)
with app.app_context():
db.create_all() # 根据模型创建所有表
return app
二、模型定义
模型(Model)是 ORM 的核心,用于映射数据库表结构。每个模型类对应一个数据库表,类的属性对应表的字段。
2.1 基础模型示例
# app/models.py
from datetime import datetime
from app import db # 导入初始化好的 SQLAlchemy 实例
class User(db.Model):
"""用户模型"""
__tablename__ = "users" # 数据库表名(不指定则默认用类名小写)
# 字段定义
id = db.Column(db.Integer, primary_key=True, autoincrement=True) # 主键,自增
username = db.Column(db.String(50), unique=True, nullable=False, comment="用户名") # 唯一且非空
email = db.Column(db.String(100), unique=True, nullable=False, comment="邮箱")
password_hash = db.Column(db.String(128), nullable=False, comment="密码哈希")
created_at = db.Column(db.DateTime, default=datetime.utcnow, comment="创建时间")
updated_at = db.Column(
db.DateTime,
default=datetime.utcnow,
onupdate=datetime.utcnow,
comment="更新时间"
)
# 关系定义(与 Article 是一对多关系)
articles = db.relationship("Article", backref="author", lazy=True)
def __repr__(self):
"""模型的字符串表示(用于调试)"""
return f"<User(id={self.id}, username='{self.username}')>"
def to_dict(self):
"""将模型实例转换为字典(用于接口返回)"""
return {
"id": self.id,
"username": self.username,
"email": self.email,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None
}
class Article(db.Model):
"""文章模型"""
__tablename__ = "articles"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(200), nullable=False, comment="文章标题")
content = db.Column(db.Text, nullable=False, comment="文章内容")
# 外键:关联 users 表的 id 字段
author_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False, comment="作者ID")
created_at = db.Column(db.DateTime, default=datetime.utcnow, comment="创建时间")
updated_at = db.Column(
db.DateTime,
default=datetime.utcnow,
onupdate=datetime.utcnow,
comment="更新时间"
)
def __repr__(self):
return f"<Article(id={self.id}, title='{self.title}')>"
def to_dict(self):
return {
"id": self.id,
"title": self.title,
"content": self.content,
"author_id": self.author_id,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None
}
2.2 模型定义关键点
__tablename__:指定数据库表名,不指定则默认使用类名的小写形式(如User对应user)。- 字段类型:
db.Column用于定义字段,第一个参数是类型(如db.Integer、db.String)。 - 约束:支持
primary_key(主键)、unique(唯一)、nullable(是否允许为空)等。 - 默认值:
default可指定默认值(如datetime.utcnow表示默认当前时间)。 - 关系定义:
db.relationship用于定义表之间的关联(如User与Article的一对多关系):backref:在关联模型中添加反向引用(如Article实例可通过author访问对应的User)。lazy:加载策略(True表示按需加载,提升性能)。
三、CRUD 核心操作
CRUD 是指创建(Create)、查询(Read)、更新(Update)、删除(Delete)四种基本操作。Flask-SQLAlchemy 提供了简洁的接口实现这些操作。
3.1 创建记录(Create)
通过实例化模型类并添加到数据库会话(session)中,最后提交会话完成创建:
# app/routes.py
from flask import Blueprint, jsonify
from app import db
from app.models import User, Article
main_bp = Blueprint("main", __name__)
@main_bp.route("/create_user", methods=["POST"])
def create_user():
# 1. 实例化模型(创建内存中的对象)
new_user = User(
username="test_user",
email="test@example.com",
password_hash="hashed_password_123" # 实际应使用加密算法(如 werkzeug.security)
)
# 2. 添加到会话
db.session.add(new_user)
try:
# 3. 提交会话(写入数据库)
db.session.commit()
return jsonify({
"code": 200,
"message": "用户创建成功",
"data": new_user.to_dict()
})
except Exception as e:
# 出错时回滚
db.session.rollback()
return jsonify({
"code": 500,
"message": f"创建失败:{str(e)}"
})
# 创建文章(关联用户)
@main_bp.route("/create_article", methods=["POST"])
def create_article():
# 先查询用户(实际应从登录状态获取)
user = User.query.filter_by(username="test_user").first()
if not user:
return jsonify({"code": 404, "message": "用户不存在"})
new_article = Article(
title="Flask-SQLAlchemy 教程",
content="这是一篇关于 Flask-SQLAlchemy 的教程...",
author_id=user.id # 关联用户ID
# 或通过关系直接赋值:author=user
)
db.session.add(new_article)
try:
db.session.commit()
return jsonify({
"code": 200,
"message": "文章创建成功",
"data": new_article.to_dict()
})
except Exception as e:
db.session.rollback()
return jsonify({"code": 500, "message": f"创建失败:{str(e)}"})
3.2 查询记录(Read)
Flask-SQLAlchemy 提供了丰富的查询方法,通过 模型类.query 调用:
# 3.2.1 基础查询
@main_bp.route("/get_user/<int:user_id>")
def get_user(user_id):
# 根据主键查询(最常用)
user = User.query.get(user_id)
if not user:
return jsonify({"code": 404, "message": "用户不存在"})
return jsonify({"code": 200, "data": user.to_dict()})
# 3.2.2 条件查询
@main_bp.route("/get_user_by_name/<string:username>")
def get_user_by_name(username):
# 精确匹配
user = User.query.filter_by(username=username).first() # 返回第一条记录
# 模糊匹配(使用 filter + 表达式)
# users = User.query.filter(User.username.like(f"%{username}%")).all() # 返回所有匹配记录
if not user:
return jsonify({"code": 404, "message": "用户不存在"})
return jsonify({"code": 200, "data": user.to_dict()})
# 3.2.3 多条件查询
@main_bp.route("/get_articles")
def get_articles():
# 查询作者ID=1且标题包含"Flask"的文章
articles = Article.query.filter(
Article.author_id == 1,
Article.title.like("%Flask%")
).all()
return jsonify({
"code": 200,
"count": len(articles),
"data": [article.to_dict() for article in articles]
})
# 3.2.4 关联查询(通过关系获取关联对象)
@main_bp.route("/get_article_with_author/<int:article_id>")
def get_article_with_author(article_id):
article = Article.query.get(article_id)
if not article:
return jsonify({"code": 404, "message": "文章不存在"})
# 通过关系获取作者信息(无需手动join)
author = article.author
return jsonify({
"code": 200,
"article": article.to_dict(),
"author": author.to_dict() if author else None
})
3.3 更新记录(Update)
查询到记录后,修改其属性并提交会话即可完成更新:
@main_bp.route("/update_user/<int:user_id>", methods=["PUT"])
def update_user(user_id):
user = User.query.get(user_id)
if not user:
return jsonify({"code": 404, "message": "用户不存在"})
# 修改属性
user.email = "updated_email@example.com"
# 其他字段也可一并修改
try:
db.session.commit() # 提交更新
return jsonify({
"code": 200,
"message": "用户更新成功",
"data": user.to_dict()
})
except Exception as e:
db.session.rollback()
return jsonify({"code": 500, "message": f"更新失败:{str(e)}"})
3.4 删除记录(Delete)
查询到记录后,通过 db.session.delete() 移除并提交会话:
@main_bp.route("/delete_article/<int:article_id>", methods=["DELETE"])
def delete_article(article_id):
article = Article.query.get(article_id)
if not article:
return jsonify({"code": 404, "message": "文章不存在"})
try:
db.session.delete(article) # 标记删除
db.session.commit() # 提交删除(实际执行 DELETE 语句)
return jsonify({"code": 200, "message": "文章删除成功"})
except Exception as e:
db.session.rollback()
return jsonify({"code": 500, "message": f"删除失败:{str(e)}"})
四、查询进阶
4.1 排序(Order By)
使用 order_by 方法对查询结果排序:
# 按创建时间降序(最新的在前)
@main_bp.route("/articles/sorted")
def sorted_articles():
# 降序:desc()
latest_articles = Article.query.order_by(Article.created_at.desc()).all()
# 升序:默认(或显式使用 asc())
earliest_articles = Article.query.order_by(Article.created_at.asc()).all()
return jsonify({
"latest": [a.to_dict() for a in latest_articles[:3]],
"earliest": [a.to_dict() for a in earliest_articles[:3]]
})
4.2 分页(Pagination)
使用 paginate 方法实现分页查询(适合大量数据):
@main_bp.route("/articles/page/<int:page>")
def paginated_articles(page=1):
# 每页显示 10 条
per_page = 10
# 分页查询
pagination = Article.query.order_by(Article.created_at.desc()).paginate(
page=page, per_page=per_page, error_out=False
)
# 分页对象属性
articles = pagination.items # 当前页数据
total = pagination.total # 总记录数
pages = pagination.pages # 总页数
current_page = pagination.page # 当前页码
return jsonify({
"code": 200,
"pagination": {
"total": total,
"pages": pages,
"current_page": current_page,
"per_page": per_page
},
"data": [a.to_dict() for a in articles]
})
4.3 聚合查询
使用 func 实现计数、求和等聚合操作:
from sqlalchemy import func
@main_bp.route("/stats")
def stats():
# 统计用户总数
user_count = db.session.query(func.count(User.id)).scalar()
# 统计每个用户的文章数
author_article_counts = db.session.query(
User.username,
func.count(Article.id).label("article_count")
).join(Article, User.id == Article.author_id).group_by(User.id).all()
return jsonify({
"user_count": user_count,
"author_article_counts": [
{"username": name, "article_count": count}
for name, count in author_article_counts
]
})
五、数据库迁移(Flask-Migrate)
在开发过程中,表结构会不断变化,直接删除重建表会导致数据丢失。Flask-Migrate 扩展可以帮助管理数据库迁移(类似 Django 的 migrations)。
5.1 安装与初始化
pip install flask-migrate
# 修改 app/__init__.py,添加迁移支持
from flask_migrate import Migrate
def create_app():
app = Flask(__name__)
app.config.from_object(Config)
db.init_app(app)
# 初始化迁移工具
migrate = Migrate(app, db)
# ... 其他代码 ...
return app
5.2 迁移命令
在项目根目录下执行以下命令(需先创建应用上下文):
# 1. 初始化迁移环境(仅首次执行)
flask db init # 会创建 migrations 目录
# 2. 生成迁移脚本(检测模型变化)
flask db migrate -m "初始迁移:创建 users 和 articles 表"
# 3. 应用迁移(更新数据库)
flask db upgrade
# 如需回滚到上一版本
flask db downgrade
六、项目结构与最佳实践
6.1 推荐项目结构
flask_demo/
├── app/
│ ├── __init__.py # 应用初始化、配置、db 实例
│ ├── models/ # 模型目录
│ │ ├── __init__.py
│ │ ├── user.py # User 模型
│ │ └── article.py # Article 模型
│ ├── routes/ # 路由目录
│ │ ├── __init__.py
│ │ ├── user_routes.py # 用户相关路由
│ │ └── article_routes.py # 文章相关路由
│ └── utils/ # 工具函数
│ └── __init__.py
├── migrations/ # 迁移文件(Flask-Migrate 生成)
├── .env # 环境变量(数据库密码等敏感信息)
├── .gitignore
├── requirements.txt # 依赖列表
└── run.py # 启动入口
6.2 最佳实践
- 配置分离:敏感信息(如数据库密码)放在
.env文件,通过python-dotenv加载,避免硬编码。 - 模型设计:
- 统一添加
created_at和updated_at字段跟踪时间。 - 实现
to_dict()方法方便接口返回 JSON 数据。
- 统一添加
- 会话管理:
- 所有数据库操作放在
try...except块中,出错时回滚(db.session.rollback())。 - 避免长时间持有会话,及时提交或回滚。
- 所有数据库操作放在
- 查询优化:
- 复杂查询使用
selectinload或joinedload减少 N+1 查询问题。 - 分页查询避免一次性加载大量数据。
- 复杂查询使用
- 安全考虑:
- 密码必须加密存储(使用
werkzeug.security.generate_password_hash)。 - 避免直接暴露模型全部字段(
to_dict()方法按需返回)。
- 密码必须加密存储(使用
七、启动与测试
创建 run.py 作为启动入口:
# run.py
from app import create_app
app = create_app()
if __name__ == "__main__":
app.run(debug=True) # 开发环境开启 debug
运行应用:
python run.py
通过 POSTMAN 或 curl 测试接口,例如:
# 创建用户
curl -X POST http://127.0.0.1:5000/create_user
# 查询用户
curl http://127.0.0.1:5000/get_user/1
总结
Flask-SQLAlchemy 简化了 Flask 应用与数据库的交互,通过 ORM 机制将 Python 对象与数据库表映射,大幅减少了手动编写 SQL 的工作量。本文从环境搭建、模型定义、CRUD 操作到查询进阶、数据库迁移,全面覆盖了其核心用法。
掌握 Flask-SQLAlchemy 的关键在于理解「模型-表」映射关系和会话管理机制,同时合理使用查询方法和迁移工具,可显著提高开发效率并保证代码质量。在实际项目中,还需结合具体业务场景进行优化,如添加索引、优化查询性能等。
2978

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



