简介
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来保证线程安全