MySQL Sqlalchemy session.refresh方法不刷新对象
如果你使用过Python中的SQLAlchemy工具包,你可能已经知道了在SQLAlchemy中使用session来操作数据库。针对session.refresh() 方法不刷新对象的问题,本文提供一些解决方案。
阅读更多:MySQL 教程
什么是session.refresh()?
在SQLAlchemy中,session是一个关键的对象,它代表着与数据库的会话。其中,session.refresh() 方法用于将当前对象的属性更新为数据库中的最新值。如果你使用了session.query().first() 方法获取了一个对象,但此后数据库中的数据被更改了,你可以使用session.refresh() 方法手动刷新对象,以便在应用中使用最新的数据。
以下是session.refresh() 方法的常用参数:
- instance: 必选参数,指定需要刷新的对象
- attribute_names: 可选参数,指定需要刷新的属性名称,它应该是一个字符串列表或者None。当attribute_names=None时,session.refresh() 方法会刷新所有属性。
为什么session.refresh()方法不刷新对象
默认情况下,当使用session.refresh() 方法时,SQLAlchemy总是尝试从数据库中获取最新的数据来刷新对象。然而,当一个对象被同一session对象多次使用时,它的状态可能会出现问题。下面将详细介绍这个问题。
引用同一个对象多次
当一个对象被引用了多次时,session.refresh() 方法可能会不工作。考虑以下例子:
from sqlalchemy.orm import sessionmaker
Session = sessionmaker()
session = Session()
class User(Base):
# 定义表结构
admin = session.query(User).filter_by(username='admin').first()
print(admin.username) # 'admin'
session.query(User).filter_by(username='admin').update({'username': 'root'})
print(admin.username) # 'admin'
session.refresh(admin)
print(admin.username) # 'admin'
在上面的例子中,当我们使用session.query().update() 更新表中的数据时,admin用户的username属性应该被更新为’root’。但是,函数调用之后我们会发现仍然是’admin’。原因是更新操作并未立即刷新在session中保存的该对象,此时admin对象已经失效,因为它没有得到数据库中‘root’所对应的数据。其实这种行为被预期,是由于SQLAlchemy session使用了identity map(身份映射)的缓存技术来减少对数据库的重复查询,从而提高查询性能。
因此,当我们执行session.refresh(admin) 方法时,我们会发现它不刷新唯一对象。
同一个事务中重复查询
如果我们多次查询同一个对象,而且这些查询是在同一个事务中执行的,就会出现类似的问题。考虑以下例子:
from sqlalchemy.orm import sessionmaker
Session = sessionmaker()
session = Session()
class User(Base):
# 定义表结构
admin = session.query(User).filter_by(username='admin').first()
print(admin.username) # 'admin'
admin = session.query(User).filter_by(username='admin').first()
print(admin.username) # 'admin'
session.query(User).filter_by(username='admin').update({'username': 'root'})
admin = session.query(User).filter_by(username='admin').first()
print(admin.username) # 'root'
session.refresh(admin)
print(admin.username) # 'admin'
在上面的例子中,我们多次查询admin对象,并在其中一次查询后更新了该对象。当我们再次查询admin对象时,我们得到了’root’作为username属性的值。然而,当我们再使用session.refresh() 方法时,我们会发现它将admin对象刷新为之前的值(‘admin’),而不是更新后的值(‘root’)。
session.expire_on_commit属性
以上两种情况下,session是把对象的状态缓存在identity map中,因此我们不能再次获取它。SQLAlchemy提供了一个简单的解决方案:设置session.expire_on_commit属性。当这个属性设置为True时,在每次的事务提交之后,session会自动将所有对象缓存刷新为数据库中的最新值。可以通过以下方式设置:
session.expire_on_commit = True
在上面的例子中,我们只需要在session对象中设置此属性,然后再执行session.query().update() 和 session.refresh() 方法即可解决问题。这样,我们就可以获得最新的数据,而不是被缓存的之前的实例。
如何正确使用session.refresh()方法
在使用session.refresh() 方法之前,我们需要确保它会刷新我们需要的对象。我们可以通过以下方式进行检查:
- 检查identity map中是否有该对象的实例;
- 每次操作之后都调用session.commit() 方法提交当前事务,然后在执行session.refresh() 方法之前查询数据库以确保该对象不在identity map缓存中。
如果使用session.expire_on_commit属性,则不需要进行这些检查。
下面是使用session.refresh() 方法的正确方式:
from sqlalchemy.orm import sessionmaker
Session = sessionmaker()
session = Session()
class User(Base):
# 定义表结构
# 检查是否有该对象的实例
admin = session.query(User).filter_by(username='admin').first()
if admin is None:
print("No such user")
else:
# 更新对象并提交事务
session.query(User).filter_by(username='admin').update({'username': 'root'})
session.commit()
# 刷新对象并进行操作
session.refresh(admin)
print(admin.username)
总结
在SQLAlchemy中,session.refresh() 方法可以将当前对象的所有属性更新为数据库的最新值。然而,当一个对象在同一session中被重复查询或引用时,可能会出现session.refresh() 方法不刷新对象的问题。这可以通过设置session.expire_on_commit属性来解决。在正确使用session.refresh() 方法之前,我们需要确保该对象的状态已经正确更新,并且在执行session.refresh() 方法之前需要仔细检查identity map中是否存在该对象的实例。