我正在尝试针对一系列数据库操作编写集成测试,并且希望能够将SQLAlchemy会话用作在其中验证和回滚事务的过渡环境。
是否可以使用session.query(Foo)
而不是session.execute(text('select * from foo'))
来检索未提交的数据?
使用SQLAlchemy 1.2.10,Python 2.7.13和Postgres 9.6.11观察到了这些结果。
我看过相关的StackOverflow帖子,但没有找到关于以下两个操作为何表现不同的解释。
SQLalchemy: changes not committing to db
session.flush()
之前尝试使用和不使用session.query
。没有成功。sqlalchemy update not commiting changes to database. Using single connection in an app
Sqlalchemy returns different results of the SELECT command (query.all)
Querying objects added to a non committed session in SQLAlchemy
session.commit()
的动机是不同的,而且我并没有找到我想要的解释。1)我建立了到数据库的连接并定义了模型对象;到目前为止没有问题:
from sqlalchemy import text
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey
#####
# Prior DB setup:
# CREATE TABLE foo (id int PRIMARY KEY, label text);
#####
# from https://docs.sqlalchemy.org/en/13/orm/mapping_styles.html#declarative-mapping
Base = declarative_base()
class Foo(Base):
__tablename__ = 'foo'
id = Column(Integer, primary_key=True)
label = Column(String)
# from https://docs.sqlalchemy.org/en/13/orm/session_basics.html#getting-a-session
some_engine = create_engine('postgresql://username:password@endpoint/database')
Session = sessionmaker(bind=some_engine)
2)我在不提交结果的情况下执行了一些更新,并且可以通过在会话中执行select语句来查看已暂存的数据:
session = Session()
sql_insert = text("INSERT INTO foo (id, label) VALUES (1, 'original')")
session.execute(sql_insert);
sql_read = text("SELECT * FROM foo WHERE id = 1");
res = session.execute(sql_read).first()
print res.label
sql_update = text("UPDATE foo SET label = 'updated' WHERE id = 1")
session.execute(sql_update)
res2 = session.execute(sql_read).first()
print res2.label
sql_update2 = text("""
INSERT INTO foo (id, label) VALUES (1, 'second_update')
ON CONFLICT (id) DO UPDATE
SET (label) = (EXCLUDED.label)
""")
session.execute(sql_update2)
res3 = session.execute(sql_read).first()
print res3.label
session.rollback()
# prints expected values: 'original', 'updated', 'second_update'
3)我尝试将select语句替换为session.query,但看不到新数据:
session = Session()
sql_insert = text("INSERT INTO foo (id, label) VALUES (1, 'original')")
session.execute(sql_insert);
res = session.query(Foo).filter_by(id=1).first()
print res.label
sql_update = text("UPDATE foo SET label = 'updated' WHERE id = 1")
session.execute(sql_update)
res2 = session.query(Foo).filter_by(id=1).first()
print res2.label
sql_update2 = text("""
INSERT INTO foo (id, label) VALUES (1, 'second_update')
ON CONFLICT (id) DO UPDATE
SET (label) = (EXCLUDED.label)
""")
session.execute(sql_update2)
res3 = session.query(Foo).filter_by(id=1).first()
print res3.label
session.rollback()
# prints: 'original', 'original', 'original'
我希望第3步的打印输出是“原始”,“更新”,“ second_update”。
答案 0 :(得分:1)
根本原因是在这种情况下原始SQL查询和ORM不会自动混合。 the Session
is not a cache表示不缓存查询,但确实根据对象的主键将对象存储在identity map中。当Query
返回映射对象的行时,将返回现有对象。这就是为什么您不观察在第三步中所做的更改的原因。这似乎是处理情况的一种较差的方法,但是SQLAlchemy的运行基于关于transaction isolation的一些假设,如"When to Expire or Refresh"中所述:
交易隔离
... [So]作为最佳猜测,它假设在事务范围内,除非已知已经发出了SQL表达式来修改特定行,否则除非有明确说明,否则无需刷新行被告知要这样做。
关于事务隔离的整个注释值得一读。使这样的更改对SQLAlchemy已知的方法是,如果可能的话,使用Query API执行更新,如果所有其他操作均失败,则手动expire进行更改的对象。考虑到这一点,您的第三步应如下所示:
session = Session()
sql_insert = text("INSERT INTO foo (id, label) VALUES (1, 'original')")
session.execute(sql_insert);
res = session.query(Foo).filter_by(id=1).first()
print(res.label)
session.query(Foo).filter_by(id=1).update({Foo.label: 'updated'},
synchronize_session='fetch')
# This query is actually redundant, `res` and `res2` are the same object
res2 = session.query(Foo).filter_by(id=1).first()
print(res2.label)
sql_update2 = text("""
INSERT INTO foo (id, label) VALUES (1, 'second_update')
ON CONFLICT (id) DO UPDATE
SET label = EXCLUDED.label
""")
session.execute(sql_update2)
session.expire(res)
# Again, this query is redundant and fetches the same object that needs
# refreshing anyway
res3 = session.query(Foo).filter_by(id=1).first()
print(res3.label)
session.rollback()