Sqlalchemy:使用装饰器为多个函数提供线程安全的会话
我正在做一个类似于这里的项目(使用sqlalchemy的多线程应用),所以我明白每次数据库查询都应该创建一个新的会话。
我在想,给每个需要访问数据库的方法加上一个装饰器是否合理,或者这样做会不会有什么陷阱。这个装饰器的构造是参考了这里的最后一个例子。
def dbconnect(func):
def inner(*args, **kwargs):
session = Session() # with all the requirements
try:
func(*args, session=session, **kwargs)
session.commit()
except:
session.rollback()
raise
finally:
session.close()
return inner
@dbconnect
def some_function(some, arguments, session)
session.query(...) # no commit, close, rollback required
some_function("many", "different_arguments")
#session is not required, since provided by decorator
这样做的话,就可以比较简单地为任何函数提供线程安全的数据库访问,而不需要重复实现整个try-except-finally的流程,但我不确定这种方法是否安全可靠,也不清楚是否有更好的实践方法。
2 个回答
7
我觉得在这里使用一个叫做 scoped_session
的东西是有道理的,可能可以这样做:
session_factory = sessionmaker(bind=some_engine)
Session = scoped_session(session_factory)
def dbconnect(func):
def inner(*args, **kwargs):
session = Session() # (this is now a scoped session)
try:
func(*args, **kwargs) # No need to pass session explicitly
session.commit()
except:
session.rollback()
raise
finally:
Session.remove() # NOTE: *remove* rather than *close* here
return inner
@dbconnect
def some_function(some, arguments):
session = Session()
# 'session' is now a handle to the *same* session as in the decorator
session.query(...) # no commit, close, rollback required
some_function("many", "different_arguments")
#session is not required, since provided by decorator
(警告:这个代码没有经过测试)
2
带有参数的装饰器很有意思,但可能会有点棘手。因为定义的参数列表和实际调用时使用的参数不一致。如果你明确地传入 session=something
,就会出现错误(不过你可以在装饰器里检查这个情况)。
你还需要至少添加一个 functools.wraps
(当然,这只是个简短的示例代码)。
事务是使用上下文管理器的一个好例子。想了解更多,可以看看这个链接:在多线程的sqlalchemy网页应用中,推荐的scoped_session使用模式是什么?。