Flask 如何在每个请求开始时启动一个新的 SQLAlchemy 事务?

tim*_*kro 5 python sqlalchemy flask

我尝试使用此方法完全分离 Flask 和 SQLAlchemy ,但 Flask 似乎仍然能够检测我的数据库并在每个请求开始时启动一个新事务。

\n\n

db.py文件创建一个新会话并定义一个简单的表模型:

\n\n
from sqlalchemy import create_engine\nfrom sqlalchemy.orm import scoped_session, sessionmaker\nfrom sqlalchemy.ext.declarative import declarative_base\nfrom sqlalchemy import Column, String\n\n\nengine = create_engine("mysql://web:kingtezdu@localhost/web_unique")\n\nprint("creating new session")\ndb_session = scoped_session(sessionmaker(bind=engine))\n\nBase = declarative_base()\nBase.query = db_session.query_property()\n\n\n# define model of \'persons\' table\nclass Person(Base):\n    __tablename__ = "persons"\n    name = Column(String(30), primary_key=True)\n\n    def __repr__(self):\n        return "Person(\\"{0.name}\\")".format(self)\n\n# create table\nBase.metadata.create_all(bind=engine)\n
Run Code Online (Sandbox Code Playgroud)\n\n

并且app.py,一个使用 SQLAlchemy 会话和模型的简单 Flask 应用程序:

\n\n
from flask import Flask, escape\napp = Flask(__name__)\n\n\n# importing new session\nfrom db import db_session, Person\n\n# registering for app teardown to remove session\n@app.teardown_appcontext\ndef shutdown_session(exception=None):\n    db_session.remove()\n\n\n@app.route("/query")\ndef query():\n    # query all persons in the database\n    all_persons = Person.query.all()\n    print all_persons\n    return "" # we use the console output\n\n\nif __name__ == "__main__":\n    app.run(debug=True)\n
Run Code Online (Sandbox Code Playgroud)\n\n

让我们运行一下:

\n\n
$ python app.py \ncreating new session\n * Running on http://127.0.0.1:5000/\n * Restarting with reloader\ncreating new session\n
Run Code Online (Sandbox Code Playgroud)\n\n

奇怪的是它运行了db.py两次,但我们忽略了这一点,让我们访问网页/query

\n\n
[]\n127.0.0.1 - - [23/Dec/2015 18:20:14] "GET /query HTTP/1.1" 200 -\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们可以看到我们的请求得到了答复,尽管我们只使用控制台输出。数据库中还没有Person,我们添加一个:

\n\n
mysql> INSERT INTO persons (name) VALUES ("Marie");\nQuery OK, 1 row affected (0.11 sec)\n
Run Code Online (Sandbox Code Playgroud)\n\n

Marie现在是数据库的一部分,因此我们重新加载网页:

\n\n
[Person("Marie")]\n127.0.0.1 - - [23/Dec/2015 18:24:48] "GET /query HTTP/1.1" 200 -\n
Run Code Online (Sandbox Code Playgroud)\n\n

正如您所看到的,会话已经知道Marie. Flask 没有创建新会话。这意味着有一个新的事务开始了。将此与下面的计划 python 示例进行对比,看看有何不同。

\n\n

我的问题是 Flask 如何能够在每个请求开始时启动一个新事务。Flask 不应该了解数据库,但似乎能够改变它的行为。

\n\n
\n\n

如果您不知道 SQLAlchemy 事务是什么,请阅读从管理事务中提取的这段内容:

\n\n
\n

当回滚或提交后事务状态完成时,会话将释放所有事务和连接资源,并返回到 xe2x80x9cbeginxe2x80x9d 状态,这将再次调用新的连接\n 和 Transaction 对象作为发出 SQL 语句的新请求被接收。

\n
\n\n

因此,事务由提交结束,并将导致建立新的连接,然后使会话再次读取数据库。实际上,这意味着当您想查看对数据库所做的更改时必须提交:

\n\n

首先在交互式Python模式下:

\n\n
>>> from db import db_session, Person\ncreating new session\n>>> Person.query.all()\n[]\n
Run Code Online (Sandbox Code Playgroud)\n\n

切换到 MySQL 并插入一个新的Person

\n\n
mysql> INSERT INTO persons (name) VALUES ("Paul");\nQuery OK, 1 row affected (0.03 sec)\n
Run Code Online (Sandbox Code Playgroud)\n\n

最后尝试加载Paul到我们的会话中:

\n\n
>>> Person.query.all()\n[]\n>>> db_session.commit()\n>>> Person.query.all()\n[Person("Paul")]\n
Run Code Online (Sandbox Code Playgroud)\n

pvg*_*pvg 2

我认为这里的问题是scoped_session在某种程度上隐藏了实际使用的会话中发生的情况。当你的拆卸处理程序

\n\n
# registering for app teardown to remove session\n@app.teardown_appcontext\ndef shutdown_session(exception=None):\n    db_session.remove()\n
Run Code Online (Sandbox Code Playgroud)\n\n

在每个请求结束时运行,您可以调用db_session.remove()它来处理该特定请求中使用的会话以及任何事务上下文。有关详细信息,请参阅http://docs.sqlalchemy.org/en/latest/orm/contextual.html ,特别是

\n\n
\n

scoped_session.remove() 方法首先对当前 Session 调用 Session.close(),其作用是首先释放 Session 拥有的任何连接/事务资源,然后丢弃 Session 本身。\xe2\x80\x9cReleasing\xe2\x80\x9d 此处表示连接将返回到其连接池,并且任何事务状态都会回滚,最终使用底层 DBAPI 连接的 rollback() 方法。

\n
\n