SQLAlchemy,scoped_session - 原始SQL插入未写入数据库
我有一个用Pyramid和SQLAlchemy做的Python应用,数据库是MySQL。 当我执行一个原始的SQL插入查询时,数据库里什么都没有写进去。 但当我使用ORM(对象关系映射)时,就可以写入数据库。我看了文档,也了解了一下ZopeTransactionExtension,还查了很多StackOverflow上的问题,但都没有解决我的问题。 到目前为止,以下方法都没有成功:
transaction.commit()
- 数据库里没有任何内容。我知道这个语句在使用ZopeTransactionExtension时是必要的,但在这里并没有起到作用。dbsession().commit
- 由于我在使用ZopeTransactionExtension,所以这个方法不管用。dbsession().close()
- 还是没有写入任何内容。dbsession().flush()
- 也没有写入。mark_changed(session)
- 这个也没用。
在文件"/home/dev/.virtualenvs/sc/local/lib/python2.7/site-packages/zope/sqlalchemy/datamanager.py"的第198行,出现了这个错误: if session.twophase: AttributeError: 'scoped_session'对象没有'twophase'这个属性。
还有一些方法是有效的,但不被接受,因为它们没有使用scoped_session:
engine.execute(...)
我想知道如何在scoped_session
(在我的代码中是dbsession()
)上执行原始SQL。
这是我的SQLAlchemy设置(models/__init__.py
)
def dbsession():
assert (_dbsession is not None)
return _dbsession
def init_engines(settings, _testing_workarounds=False):
import zope.sqlalchemy
extension = zope.sqlalchemy.ZopeTransactionExtension()
global _dbsession
_dbsession = scoped_session(
sessionmaker(
autoflush=True,
expire_on_commit=False,
extension=extension,
)
)
engine = engine_from_config(settings, 'sqlalchemy.')
_dbsession.configure(bind=engine)
我写了一个Python脚本来隔离这个问题。这个脚本模拟了问题发生的真实环境。我只想让下面的脚本把数据插入到数据库中:
# -*- coding: utf-8 -*-
import sys
import transaction
from pyramid.paster import setup_logging, get_appsettings
from sc.models import init_engines, dbsession
from sqlalchemy.sql.expression import text
def __main__():
if len(sys.argv) < 2:
raise RuntimeError()
config_uri = sys.argv[1]
setup_logging(config_uri)
aa = init_engines(get_appsettings(config_uri))
session = dbsession()
session.execute(text("""INSERT INTO
operations (description, generated_description)
VALUES ('hello2', 'world');"""))
print list(session.execute("""SELECT * from operations""").fetchall()) # prints inserted data
transaction.commit()
print list(session.execute("""SELECT * from operations""").fetchall()) # doesn't print inserted data
if __name__ == '__main__':
__main__()
有趣的是,如果我这样做:
session = dbsession()
session.execute(text("""INSERT INTO
operations (description, generated_description)
VALUES ('hello2', 'world');"""))
op = Operation(generated_description='aa', description='oo')
session.add(op)
那么第一个打印输出的是插入的原始SQL行('hello2' 'world'),第二个打印输出的是两个行,实际上这两行都被插入到数据库里。
我无法理解为什么在使用ORM插入的同时,原始SQL的使用会“修复”这个问题。
我真的需要能够在scoped_session上调用execute(),以便使用原始SQL将数据插入数据库。有什么建议吗?
1 个回答
已经有一段时间没把原始SQL和SQLAlchemy混合使用了,但每当你这样做时,需要了解ORM背后发生了什么。首先,检查一下自动提交的设置。如果zope事务没有正确配置,ORM的插入操作可能会触发一个提交。
实际上,查看了zope的文档后,发现手动执行语句需要额外的步骤。从他们的说明文件中可以看到:
默认情况下,zope.sqlalchemy在第一次使用会话时将其置于“活动”状态。ORM的写操作会自动将会话移到“已更改”状态。这可以避免不必要的数据库提交。有时需要通过SQL直接与数据库交互。我们无法猜测这样的操作是读取还是写入。因此,当手动SQL语句写入数据库时,我们必须手动将会话标记为已更改。
>>> session = Session()
>>> conn = session.connection()
>>> users = Base.metadata.tables['test_users']
>>> conn.execute(users.update(users.c.name=='bob'), name='ben')
<sqlalchemy.engine...ResultProxy object at ...>
>>> from zope.sqlalchemy import mark_changed
>>> mark_changed(session)
>>> transaction.commit()
>>> session = Session()
>>> str(session.query(User).all()[0].name)
'ben'
>>> transaction.abort()
看起来你没有这样做,所以transaction.commit没有任何作用。