我有一个 Django REST Framework 序列化程序,它使用
select_for_update
结合原子转换,像这样:https://docs.djangoproject.com/en/4.2/ref/models/querysets/#select-for-update/ .这工作正常,除了我想在抛出错误时向数据库写入一些东西......并且这些插入语句正在回滚,永远不会进入数据库。
代码是这样的(非常简化,但可以理解并可重现):
from django.db import models
class LicenseCode(models.Model):
code = models.CharField(max_length=200, unique=True)
class Activation(models.Model):
license_code = models.TextField(max_length=200)
activation_log = models.TextField(blank=True, null=True)
success = models.BooleanField(default=False)
from django.http import HttpResponse, Http404
from django.db import transaction
from .models import Activation, LicenseCode
class LicenseRedeemSerializer:
@transaction.atomic
def save(self):
license_codes = LicenseCode.objects.all().select_for_update()
activations = []
for license_code in license_codes:
activations.append(
Activation(license_code=license_code.code, success=False)
)
self.activate_licenses(activations)
def activate_licenses(self, activations):
try:
# In our real app we'd try to activate the licenses with an external SDK. This can fail.
raise Exception("Something went wrong!")
except Exception as e:
for activation in activations:
activation.activation_log = str(e)
activation.save()
# With our real DRF serializer we'd raise ValidationError
raise Http404("Could not activate the license!")
def view(request):
# Let's make sure we have a license code to work with
LicenseCode.objects.get_or_create(code="A")
serialier = LicenseRedeemSerializer()
serialier.save()
html = "Hello there"
return HttpResponse(html)
我面临的问题是,当外部 SDK 触发错误并且我试图向数据库写入一些内容时,这永远不会在数据库中结束,事务只是回滚。
如何确保在使用原子事务时仍然可以在 except 块中向数据库写入内容?
您可以使用上下文管理器代替装饰器。例如:
class MySerializer(Serializer):
# Some serializer fields here..
def save(self):
try:
with transaction.atomic():
foo = Foo.objects.all().select_for_update()
SomeExternalSDK.doStuff(foo)
except SomeExternalSDK.SomeException as e:
log = Log(message="Uh oh")
log.save()
raise ValidationError("Something went wrong")
atomic()
文档中提到的,它将在异常时回滚事务,因此我认为没有办法在错误期间将信息直接存储在数据库中。
但是你总是可以在原子块之外捕获异常,然后保存错误并像这样重新引发错误:
class MySerializer(Serializer):
# Some serializer fields here..
def save(self):
try:
self.save_data()
except ValidationError as e:
Log.objects.create(error = str(e))
raise e
@transaction.atomic
def save_data(self):
foo = Foo.objects.all().select_for_update()
self.do_something(foo)
self.do_something_else(foo)
def do_something(self, foo):
try:
SomeExternalSDK.doStuff(foo)
except SomeExternalSDK.SomeException as e:
raise ValidationError(str(e))
def self.do_something_else(self, foo):
pass
或者,您可以创建一个对象变量(如列表),您可以在其中放置 SDK 中发生的异常,然后将这些错误存储在数据库中。
通常,日志不存储在数据库中,而是存储在文件中。您可以考虑将错误存储在文件系统中,而不是将错误存储在数据库中,或者使用 Django 的日志记录 来存储错误。