如何在Django中循环中回滚事务

3
我正在尝试在循环中抛出异常时回滚一组事务。但是我不想跳出循环或抛出未捕获的异常。 如果循环中的任何一个子元素抛出异常,我不希望将业务逻辑保存。这意味着我不能将事务放在循环内部,因为如果其中任何一个失败,它只会回滚特定子元素的事务。
parent = Parent.objects.get(pk='something')
exceptions = []
with transaction.atomic():
    for child in parent.children.all():
        try:
            # business logic which also saves other models
            # I don't want this saved if there is an exception for any object in the loop
        except Exception as e:
            exceptions.append({
                'id': child.id,
                'error': str(e),
            })
if len(exceptions) > 0:
    transaction.set_rollback(True)
    for exception in exceptions:
        Child.objects.filter(pk=exception['id']) \
            .update(error=exception['error']
    # more business logic and raise exception
    parent.is_blocked = True
    parent.save()
    # I don't want this exception to rollback all transactions
    raise Exception('Parent {} is blocked'.format(parent.id))

我在上面的代码中遇到了一个错误。信息非常明确。我试图在块外回滚事务。

django.db.transaction.TransactionManagementError: 回滚标志在“原子”块之外不起作用。

有人找到了处理这种情况的方法吗?我希望我只是错过了一些简单的东西。如果您需要更多信息,请告诉我。

2个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
1

避免在原子操作内捕获异常!

按照文档,对于您的特殊情况,您的代码应该像这样:

parent = Parent.objects.get(pk='something')
exceptions = []
try:
    with transaction.atomic():
        for child in parent.children.all():
            try:
                # business logic which also saves other models
                # I don't want this saved if there is an exception for any object in the loop
            except Exception as e:
                exceptions.append({
                    'id': child.id,
                    'error': str(e),
                })
        # raise exception handly to trigger rollback
        if len(exceptions) > 0:
            raise("raise for rollback")
except Exception as e:
    pass

if len(exceptions) > 0:
    for exception in exceptions:
        Child.objects.filter(pk=exception['id']) \
            .update(error=exception['error']
    # more business logic and raise exception
    parent.is_blocked = True
    parent.save()
    # I don't want this exception to rollback all transactions
    raise Exception('Parent {} is blocked'.format(parent.id))

0
你可以尝试使用生成器函数:
def function():
    for child in parent.children.all():
        try:
            yield result
        except Exception as e:
            yield exception

如果想了解清晰,请查看这个答案: 如何处理生成器函数中抛出的错误


您介意再解释一下吗?如果有任何异常抛出,这如何解决回滚事务的问题? - brandonbanks
生成器函数基本上在发生错误时返回异常,因此如果您检查我向您展示的链接(它是一个已回答的问题),将会看到如何实现它的更好解释。 - Samuel Omole
我很感激这个想法。但我认为这并不能解决问题。也许我有所遗漏,但我已经捕获了异常并将其添加到了一个数组中。 - brandonbanks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,