简介

Flask-SQLAlchemy 使用起来非常有趣,对于基本应用十分容易使用,并且对于大型项目易于扩展。

常见情况下对于只有一个 Flask 应用,所有您需要做的事情就是创建 Flask 应用,选择加载配置接着创建 SQLAlchemy 对象时候把 Flask 应用传递给它作为参数。

基本操作:

pip3 install flask-sqlalchemy
# __init__.py:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
#第一步:在__init__中创建db对象,包含了SQLALchemy的所有操作。
db = SQLAlchemy()

def create_app():

    app = Flask(__name__)
    app.debug = True
    app.config.from_object('settings.DevelopmentConfig')

    from .views import account
    app.register_blueprint(account.ac)
  
# 第二步: 在__init___中将app传入到db中。
    db.init_app(app)
    return app
# settings.py:第三步:将连接字符串的定义写在配置文件中
class BaseConfig(object):
    # SESSION_TYPE = 'redis'  # session类型为redis
    # SESSION_KEY_PREFIX = 'session:'  # 保存到session中的值的前缀
    # SESSION_PERMANENT = True  # 如果设置为False,则关闭浏览器session就失效。
    # SESSION_USE_SIGNER = False  # 是否对发送到浏览器上 session:cookie值进行加密
    SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root@127.0.0.1:3306/userinfo3?charset=utf8"
    SQLALCHEMY_POOL_SIZE = 5
    SQLALCHEMY_POOL_TIMEOUT = 30
    SQLALCHEMY_POOL_RECYCLE = -1

    # 追踪对象的修改并且发送信号
    SQLALCHEMY_TRACK_MODIFICATIONS = False


class ProductionConfig(BaseConfig):
    pass


class DevelopmentConfig(BaseConfig):
    pass

class TestingConfig(BaseConfig):
    pass
models:
from sqlalchemy import Column, Integer, String, UniqueConstraint, Index, DATETIME, ForeignKey
from flask2 import db

# 第四步:定义数据库表
class Users(db.Model):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(32), unique=True, nullable=False)

class School(db.Model):
    __tablename__ = 'school'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(32), unique=True, nullable=False)
# 第五步: 创建离线脚本: drop_create_table.py
"""
Web运行时,flask程序运行起来,用户通过浏览器访问
离线脚本,自定义的一个py文件+使用flask中定义好的功能
"""

from flask2 import db, create_app,models


app = create_app()
with app.app_context():
    # db.drop_all()
    db.create_all()
    # data = db.session.query(models.Users).all()
    # print(data)
# 第六步: 在视图函数中使用SQLAlchemy:
from .. import models
from flask import Blueprint

from flask2 import db

ac = Blueprint('ac',__name__)


@ac.route('/login',methods=['GET', 'POST'])
def login():

    ret = db.session.query(models.Users).all()
    print(ret[0].name)
    db.session.remove()

    return 'login'

Session 的生命周期

首先我们需要知道一个 sqlalchemy session 的生命周期是怎样的。我们的 web 应用会同时服务多个用户,因此不同的请求要有不同的 session,不然就会翻车。

session 会在一次请求进来的时候创建出来

session = Session()
在整个请求处理过程中被使用

session.add(some_obj)
session.commit()
在请求处理完毕后被关闭销毁。

session.close()

当然上面的代码只是简略的情形,通常还需要包括 try-except 来处理 session 需要 rollback 之类的情况。

scoped_session 与 registry 模式

scoped_session就是用在 web 应用这种处理请求的场景下,协助进行 session 维护的。

from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session
engine = create_engine("mysql+pymysql://root@127.0.0.1:3306/userinfo3?charset=utf8",pool_size=2, max_overflow=0)
session_factory  = sessionmaker(bind=engine)
session = scoped_session(session_factory )

通常我们在初始化 web 应用时通过scoped_session函数对原始的Session工厂进行处理,返回出一个ScopedSession工厂,在每个请求来的时候就可以通过这个工厂获得一个 scoped_session 对象。

这实际上实现了一种 registry 模式,它有个中心化的 registry 来保存已经创建的 session,并在你调用ScopedSession工厂的时候,在 registry 里面找找是不是之前已经为你创建过 session 了,如果有,就直接把这个 session 返回给你,如果没有,就创建一个新的 session,并注册到 registry 中以便你下次来要的时候给你。

Thread-Local Storage

上面说到 scoped_session 类似单例模式,我们看似创建了新的 session,实际上拿到的可能是之前创建出来的 session。但我们 web 应用通常要同时处理多个请求,我的请求有没有可能不小心拿到别人创建的 session 对象呢?

尽管 Python 有着神奇的 GIL,没法真正的并行地跑线程,但至少还是有线程的概念的,对于不同的请求进来的时候我们通常会在不同的线程中进行处理。Python 里有个概念叫 thread local storage(TLS),即线程本地存储,它可以作为全局变量一样使用,但这个数据只在这个线程中有效,与其他的线程是隔离的。

import threading
mydata = threading.local()
mydata.x = 1

这不正好和刚才 registry 模式的思想差不多嘛。和我们所希望的一样,scoped_session 也确实使用了 tls 作为 session 的存储方式,一个线程只能拿到自己创建出来的 session,保证了不同线程不会乱入别人的 session。

使用 tls 还有另外一个好处,由于 session 是跟着线程走的,就算你没有调用remove()亲手干掉 session,也会由于线程结束,session 也跟着被一起回收掉,不至于泄漏。(但仍建议在必要的时候对资源进行显式的回收)

还有一个隐蔽的问题,如果我们用了 gevent 来处理并发而不是用多线程,会翻车吗?答案是不会。gevent 在monkey.patch_all()的时候,已经悄悄把这个 threading 相关的东西悄悄替换成自己的一套了,thread-local 的东西已经变成了 greenlet-local,不同协程间仍是隔离的,一般不会有问题。

一些思路

如果你是纯Flask应用,不涉及任何视图函数之外的数据库逻辑,请直接使用flask-sqlalchemy。它封装的session默认是scoped_session。

如果还有很多外部代码,一定保证你的session是可控的。 简单来说就是:

  • 用就开,用完就关
  • 尽量避免多个函数依赖之间同时使用session
  • 使用scoped_session来保证线程安全