Django - 使用事务原子性回滚保存

67

我正在尝试创建一个视图,在保存对象时,如果出现异常,我想要撤销该操作。这是我尝试的代码:

class MyView(View):

    @transaction.atomic
    def post(self, request, *args, **kwargs):
        try:
            some_object = SomeModel(...)
            some_object.save()

            if something:
                raise exception.NotAcceptable()
                # When the workflow comes into this condition, I think the previous save should be undone
                # What am I missing?

        except exception.NotAcceptable, e:
            # do something

我做错了什么?即使出现异常,some_object 仍然在数据库中。

3个回答

83

原子性文档

简而言之,当您的视图产生无错误响应时,@transaction.atomic会在数据库上执行事务。由于您自己捕获异常,因此 Django 认为您的视图执行得非常好。

如果您捕获了异常,则需要自行处理:控制事务

如果在发生故障时需要生成适当的 JSON 响应:

from django.db import SomeError, transaction

def viewfunc(request):
    do_something()

    try:
        with transaction.atomic():
            thing_that_might_fail()
    except SomeError:
        handle_exception()

    render_response()

这个视图是为 API 设计的,因此我认为需要处理任何可能的错误,并返回一个适当的 JSON 响应。那么使用原子装饰器是不可行的吗? - Gocht
不使用装饰器,因为它在函数外处理事务。上下文管理器的示例非常好! - jpic
你必须在try.. except块内部使用原子块,就像答案中所示。如果需要,也可以在视图上使用原子装饰器。 - Alasdair
1
实际上,看起来你不应该使用装饰器。你可能希望响应根据保存的成功与否而有所不同。因此,你应该像我在示例中所做的那样去做,然后你就可以轻松地知道你是否成功了。如果响应不取决于执行的成功与否,则使用装饰器并且不要捕获/处理异常(删除try/except块)。 - jlucier
4
不,如果你想捕获异常,你必须在try..except块内使用with transaction.atomic。我的观点是,你可以同时为视图使用@transaction.atomic。这仍然很有用,如果你想要do_something()和try..except块的结果在一个事务中,要么成功要么失败。 - Alasdair
显示剩余2条评论

18

如果一个被@transaction.atomic修饰的函数中发生了异常,那么你无需进行任何操作,它会自动回滚到运行该函数之前由装饰器创建的保存点,如文档所述:

原子性允许我们创建一个代码块,在其中数据库的原子性得到保证。如果代码块成功完成,则将更改提交到数据库。如果出现异常,则将更改回滚。

如果在except块中捕获到异常,则应重新引发该异常以供原子性捕获并执行回滚,例如:

    try:
        some_object = SomeModel(...)
        some_object.save()

        if something:
            raise exception.NotAcceptable()
            # When the workflow comes into this condition, I think the previous save should be undome
            # Whant am I missing?

    except exception.NotAcceptable, e:
        # do something
        raise  # re-raise the exception to make transaction.atomic rollback

另外,如果您希望更多控制权,您可以手动回滚到之前设置的保存点,如下所示:

previously set savepoint
class MyView(View):
    def post(self, request, *args, **kwargs):
        sid = transaction.savepoint()
        some_object = SomeModel(...)
        some_object.save()

        if something:
            transaction.savepoint_rollback(sid)
        else:
            try:
                # In worst case scenario, this might fail too
                transaction.savepoint_commit(sid)
            except IntegrityError:
                transaction.savepoint_rollback(sid)

这就是我想的,也是为什么我用这种方式编写了这个函数,但正如我在问题中所说的那样,即使出现异常,对象仍然存在于数据库中,是否还需要采取其他步骤来使用保存点? - Gocht
可能是因为异常被 except 块捕获而没有重新引发,所以 Atomic 认为函数执行成功了。 - jpic
如果我没有捕获错误,我就无法给出良好的响应。我该如何构建我的函数? - Gocht
增加了一个保存点回滚的示例,但我认为上下文管理器在这种情况下可能更合适。 - jpic
在发生故障的情况下,使用try catch和保存点不会回滚任何事务。有人可以告诉我如何像下面链接中所要求的那样使用保存点和try-catch吗?https://stackoverflow.com/questions/60314464/django-unable-to-rollback-with-try-exception-block-for-atomic-transactions/60315686#60315686 - shreesh katti

5

对于我来说,这在Django 2.2.5中有效。

首先,在您的settings.py文件中:

...

DATABASES = {
    'default': {
        'ENGINE': 'xxx',  # transactional db
        ...
        'ATOMIC_REQUESTS': True,
    }
}

并在你的函数中(views.py)

from django.db import transaction

@transaction.atomic
def make_db_stuff():

    # do stuff in your db (inserts or whatever)

    if success:
        return True
    else:
        transaction.set_rollback(True)
        return False

11
ATOMIC_REQUESTS设置为True会使得所有视图都具有原子性 - 因此,您应该将其设置为真,并在您想要排除的视图上使用@transaction.non_atomic_requests,或者通过@transaction.atomic单独将视图设置为原子。来源:https://docs.djangoproject.com/en/3.0/topics/db/transactions/#tying-transactions-to-http-requests - Hybrid
在我看来,transaction.set_rollback似乎可以独立使用,无需ATOMIC_REQUESTS。 - undefined

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