在Django中使用嵌套的@ transaction.commit_on_success

时间:2013-02-12 12:28:28

标签: django transactions

考虑这个简单的例子:

# a bank account class
class Account:
    @transaction.commit_on_success
    def withdraw(self, amount):
        # code to withdraw money from the account 

    @transaction.commit_on_success
    def add(self, amount):
        # code to add money to the account

# somewhere else
@transaction.commit_on_success
def makeMoneyTransaction(src_account, dst_account, amount):
    src_account.withdraw(amount)
    dst_account.add(amount)

(摘自https://code.djangoproject.com/ticket/2227

如果Account.add()中出现异常,Account.withdraw()中的事务仍将被提交,资金将丢失,因为Django目前不处理嵌套事务。

如果不对Django应用补丁,我们如何确保将提交发送到数据库,但只有当@transaction.commit_on_success装饰器下的主函数完成而没有引发异常时才会发送?

我遇到了这个片段:http://djangosnippets.org/snippets/1343/,似乎它可以完成这项工作。如果我使用它,我应该注意哪些缺点?

如果你能提供帮助,请提前感谢。

P.S。为了提高可见性,我正在复制之前引用的代码段:

def nested_commit_on_success(func):
    """Like commit_on_success, but doesn't commit existing transactions.

    This decorator is used to run a function within the scope of a 
    database transaction, committing the transaction on success and
    rolling it back if an exception occurs.

    Unlike the standard transaction.commit_on_success decorator, this
    version first checks whether a transaction is already active.  If so
    then it doesn't perform any commits or rollbacks, leaving that up to
    whoever is managing the active transaction.
    """
    commit_on_success = transaction.commit_on_success(func)
    def _nested_commit_on_success(*args, **kwds):
        if transaction.is_managed():
            return func(*args,**kwds)
        else:
            return commit_on_success(*args,**kwds)
    return transaction.wraps(func)(_nested_commit_on_success)

2 个答案:

答案 0 :(得分:5)

此代码段的问题在于,它不能让您在不回滚外部事务的情况下回滚内部事务。例如:

@nested_commit_on_success
def inner():
    # [do stuff in the DB]

@nested_commit_on_success
def outer():
    # [do stuff in the DB]
    try:
        inner()
    except:
        # this did not work, but we want to handle the error and
        # do something else instead:

        # [do stuff in the DB]

outer()

在上面的示例中,即使inner()引发异常,也不会回滚其内容。

您需要的是内部“交易”的savepoint。对于您的代码,它可能如下所示:

# a bank account class
class Account:
    def withdraw(self, amount):
        sid = transaction.savepoint()
        try:
            # code to withdraw money from the account
        except:
            transaction.savepoint_rollback(sid)
            raise

    def add(self, amount):
        sid = transaction.savepoint()
        try:
            # code to add money to the account
        except:
            transaction.savepoint_rollback(sid)
            raise

# somewhere else
@transaction.commit_on_success
def makeMoneyTransaction(src_account, dst_account, amount):
    src_account.withdraw(amount)
    dst_account.add(amount)

从Django 1.6开始,atomic()装饰器就是这样做的:它使用一个事务用于外部使用装饰器,任何内部使用都使用一个保存点。

答案 1 :(得分:2)

Django 1.6引入了@atomic,它完全符合我的要求!

它不仅支持“嵌套”事务,而且还取代了旧的,功能较弱的装饰器。对于跨不同Django应用程序的事务管理,有一个独特且一致的行为是很好的。