Python如何使用Flask-SQLAlchemy库来简化Flask应用与数据库的交互

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.Integerdb.String)。
  • 约束:支持 primary_key(主键)、unique(唯一)、nullable(是否允许为空)等。
  • 默认值default 可指定默认值(如 datetime.utcnow 表示默认当前时间)。
  • 关系定义db.relationship 用于定义表之间的关联(如 UserArticle 的一对多关系):
    • 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 最佳实践

  1. 配置分离:敏感信息(如数据库密码)放在 .env 文件,通过 python-dotenv 加载,避免硬编码。
  2. 模型设计
    • 统一添加 created_atupdated_at 字段跟踪时间。
    • 实现 to_dict() 方法方便接口返回 JSON 数据。
  3. 会话管理
    • 所有数据库操作放在 try...except 块中,出错时回滚(db.session.rollback())。
    • 避免长时间持有会话,及时提交或回滚。
  4. 查询优化
    • 复杂查询使用 selectinloadjoinedload 减少 N+1 查询问题。
    • 分页查询避免一次性加载大量数据。
  5. 安全考虑
    • 密码必须加密存储(使用 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 的关键在于理解「模型-表」映射关系和会话管理机制,同时合理使用查询方法和迁移工具,可显著提高开发效率并保证代码质量。在实际项目中,还需结合具体业务场景进行优化,如添加索引、优化查询性能等。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值